From 47afbcfb3be533873ed60d42f598dc02a53d3647 Mon Sep 17 00:00:00 2001 From: Nathan Grennan Date: Sun, 30 Mar 2025 15:17:47 -0700 Subject: [PATCH 01/99] Broke the MainPagerAdapter class out of MainActivity.java --- .../redditslide/Activities/MainActivity.java | 217 +-------------- .../Activities/MainPagerAdapter.java | 260 ++++++++++++++++++ 2 files changed, 269 insertions(+), 208 deletions(-) create mode 100644 app/src/main/java/me/edgan/redditslide/Activities/MainPagerAdapter.java diff --git a/app/src/main/java/me/edgan/redditslide/Activities/MainActivity.java b/app/src/main/java/me/edgan/redditslide/Activities/MainActivity.java index c97fb33b2..69e84b6f0 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/MainActivity.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/MainActivity.java @@ -4,7 +4,6 @@ import android.animation.Animator; import android.animation.AnimatorListenerAdapter; -import android.animation.ArgbEvaluator; import android.animation.ValueAnimator; import android.app.Activity; import android.app.Dialog; @@ -22,7 +21,6 @@ import android.graphics.Color; import android.graphics.Point; import android.graphics.PorterDuff; -import android.graphics.drawable.ColorDrawable; import android.net.Uri; import android.os.AsyncTask; import android.os.Build; @@ -43,7 +41,6 @@ import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; -import android.view.ViewGroup; import android.view.ViewStub; import android.view.ViewTreeObserver; import android.view.Window; @@ -85,7 +82,6 @@ import androidx.drawerlayout.widget.DrawerLayout; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; -import androidx.fragment.app.FragmentStatePagerAdapter; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.viewpager.widget.ViewPager; @@ -203,7 +199,7 @@ public class MainActivity extends BaseActivity public static boolean isRestart; public static int restartPage; public final long ANIMATE_DURATION = 250; // duration of animations - private final long ANIMATE_DURATION_OFFSET = 45; // offset for smoothing out the exit animations + final long ANIMATE_DURATION_OFFSET = 45; // offset for smoothing out the exit animations public boolean singleMode; public ToggleSwipeViewPager pager; public CaseInsensitiveArrayList usedArray; @@ -257,7 +253,7 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) { current = current - 1; } if (current < 0) current = 0; - adapter = new MainPagerAdapter(getSupportFragmentManager()); + adapter = new MainPagerAdapter(this, getSupportFragmentManager()); pager.setAdapter(adapter); pager.setCurrentItem(current); if (mTabLayout != null) { @@ -3947,9 +3943,9 @@ public void reloadSubs() { reloadItemNumber = current; if (adapter instanceof MainPagerAdapterComment) { pager.setAdapter(null); - adapter = new MainPagerAdapterComment(getSupportFragmentManager()); + adapter = new MainPagerAdapterComment(this, getSupportFragmentManager()); } else { - adapter = new MainPagerAdapter(getSupportFragmentManager()); + adapter = new MainPagerAdapter(this, getSupportFragmentManager()); } pager.setAdapter(adapter); @@ -4007,7 +4003,7 @@ public void run() { usedArray = new CaseInsensitiveArrayList( UserSubscriptions.getSubscriptions(MainActivity.this)); - adapter = new MainPagerAdapter(getSupportFragmentManager()); + adapter = new MainPagerAdapter(MainActivity.this, getSupportFragmentManager()); pager.setAdapter(adapter); if (mTabLayout != null) { @@ -4099,9 +4095,9 @@ public void setDataSet(List data) { usedArray = new CaseInsensitiveArrayList(data); if (adapter == null) { if (commentPager && singleMode) { - adapter = new MainPagerAdapterComment(getSupportFragmentManager()); + adapter = new MainPagerAdapterComment(this, getSupportFragmentManager()); } else { - adapter = new MainPagerAdapter(getSupportFragmentManager()); + adapter = new MainPagerAdapter(this, getSupportFragmentManager()); } } else { adapter.notifyDataSetChanged(); @@ -4997,209 +4993,14 @@ public void onSingleClick(View v) { } } - public class MainPagerAdapter extends FragmentStatePagerAdapter { - protected SubmissionsView mCurrentFragment; - - public MainPagerAdapter(FragmentManager fm) { - super(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT); - - pager.clearOnPageChangeListeners(); - pager.addOnPageChangeListener( - new ViewPager.SimpleOnPageChangeListener() { - @Override - public void onPageScrolled( - int position, float positionOffset, int positionOffsetPixels) { - if (positionOffset == 0) { - header.animate() - .translationY(0) - .setInterpolator(new LinearInterpolator()) - .setDuration(180); - doSubSidebarNoLoad(usedArray.get(position)); - } - } - - @Override - public void onPageSelected(final int position) { - Reddit.currentPosition = position; - selectedSub = usedArray.get(position); - SubmissionsView page = (SubmissionsView) adapter.getCurrentFragment(); - - if (hea != null) { - hea.setBackgroundColor(Palette.getColor(selectedSub)); - if (accountsArea != null) { - accountsArea.setBackgroundColor( - Palette.getDarkerColor(selectedSub)); - } - } - - int colorFrom = ((ColorDrawable) header.getBackground()).getColor(); - int colorTo = Palette.getColor(selectedSub); - - ValueAnimator colorAnimation = - ValueAnimator.ofObject(new ArgbEvaluator(), colorFrom, colorTo); - - colorAnimation.addUpdateListener( - new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator animator) { - int color = (int) animator.getAnimatedValue(); - - header.setBackgroundColor(color); - - if (Build.VERSION.SDK_INT - >= Build.VERSION_CODES.LOLLIPOP) { - int finalColor = Palette.getDarkerColor(color); - - if (SettingValues.alwaysBlackStatusbar) { - finalColor = Color.BLACK; - } - - getWindow().setStatusBarColor(finalColor); - - if (SettingValues.colorNavBar) { - getWindow() - .setNavigationBarColor( - Palette.getDarkerColor(color)); - } - } - } - }); - colorAnimation.setInterpolator(new AccelerateDecelerateInterpolator()); - colorAnimation.setDuration(200); - colorAnimation.start(); - - setRecentBar(selectedSub); - - if (SettingValues.single || mTabLayout == null) { - // Smooth out the fading animation for the toolbar subreddit search - // UI - if ((SettingValues.subredditSearchMethod - == Constants.SUBREDDIT_SEARCH_METHOD_TOOLBAR - || SettingValues.subredditSearchMethod - == Constants.SUBREDDIT_SEARCH_METHOD_BOTH) - && findViewById(R.id.toolbar_search).getVisibility() - == View.VISIBLE) { - new Handler() - .postDelayed( - new Runnable() { - @Override - public void run() { - getSupportActionBar() - .setTitle(selectedSub); - } - }, - ANIMATE_DURATION + ANIMATE_DURATION_OFFSET); - } else { - getSupportActionBar().setTitle(selectedSub); - } - } else { - mTabLayout.setSelectedTabIndicatorColor( - new ColorPreferences(MainActivity.this) - .getColor(selectedSub)); - } - if (page != null && page.adapter != null) { - SubredditPosts p = page.adapter.dataSet; - if (p.offline && !isRestart) { - p.doMainActivityOffline(MainActivity.this, p.displayer); - } - } - } - }); - - if (pager.getAdapter() != null) { - pager.getAdapter().notifyDataSetChanged(); - pager.setCurrentItem(1); - pager.setCurrentItem(0); - } - } - - @Override - public int getCount() { - if (usedArray == null) { - return 1; - } else { - return usedArray.size(); - } - } - - @NonNull - @Override - public Fragment getItem(int i) { - SubmissionsView f = new SubmissionsView(); - Bundle args = new Bundle(); - String name; - if (multiNameToSubsMap.containsKey(usedArray.get(i))) { - name = multiNameToSubsMap.get(usedArray.get(i)); - } else { - name = usedArray.get(i); - } - args.putString("id", name); - f.setArguments(args); - - return f; - } - - @Override - public void setPrimaryItem( - @NonNull ViewGroup container, int position, @NonNull Object object) { - if (reloadItemNumber == position || reloadItemNumber < 0) { - super.setPrimaryItem(container, position, object); - if (usedArray.size() >= position) doSetPrimary(object, position); - } else { - shouldLoad = usedArray.get(reloadItemNumber); - if (multiNameToSubsMap.containsKey(usedArray.get(reloadItemNumber))) { - shouldLoad = multiNameToSubsMap.get(usedArray.get(reloadItemNumber)); - } else { - shouldLoad = usedArray.get(reloadItemNumber); - } - } - } - - @Override - public Parcelable saveState() { - return null; - } - - public void doSetPrimary(Object object, int position) { - if (object != null - && getCurrentFragment() != object - && position != toOpenComments - && object instanceof SubmissionsView) { - shouldLoad = usedArray.get(position); - if (multiNameToSubsMap.containsKey(usedArray.get(position))) { - shouldLoad = multiNameToSubsMap.get(usedArray.get(position)); - } else { - shouldLoad = usedArray.get(position); - } - - mCurrentFragment = ((SubmissionsView) object); - if (mCurrentFragment.posts == null && mCurrentFragment.isAdded()) { - mCurrentFragment.doAdapter(); - } - } - } - - public Fragment getCurrentFragment() { - return mCurrentFragment; - } - - @Override - public CharSequence getPageTitle(int position) { - if (usedArray != null) { - return StringUtil.abbreviate(usedArray.get(position), 25); - } else { - return ""; - } - } - } public class MainPagerAdapterComment extends MainPagerAdapter { public int size = usedArray.size(); public Fragment storedFragment; private CommentPage mCurrentComments; - public MainPagerAdapterComment(FragmentManager fm) { - super(fm); + public MainPagerAdapterComment(MainActivity mainActivity, FragmentManager fm) { + super(mainActivity, fm); pager.clearOnPageChangeListeners(); pager.addOnPageChangeListener( new ViewPager.SimpleOnPageChangeListener() { diff --git a/app/src/main/java/me/edgan/redditslide/Activities/MainPagerAdapter.java b/app/src/main/java/me/edgan/redditslide/Activities/MainPagerAdapter.java new file mode 100644 index 000000000..b9392dd7b --- /dev/null +++ b/app/src/main/java/me/edgan/redditslide/Activities/MainPagerAdapter.java @@ -0,0 +1,260 @@ +package me.edgan.redditslide.Activities; + +import android.graphics.drawable.ColorDrawable; +import android.os.Build; +import android.os.Bundle; +import android.os.Handler; +import android.os.Parcelable; +import android.util.Log; +import android.view.View; +import android.view.ViewGroup; +import android.view.animation.AccelerateDecelerateInterpolator; +import android.view.animation.LinearInterpolator; +import android.animation.ArgbEvaluator; +import android.animation.ValueAnimator; + +import androidx.annotation.NonNull; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; +import androidx.fragment.app.FragmentStatePagerAdapter; +import androidx.viewpager.widget.ViewPager; + + + +import me.edgan.redditslide.Adapters.SubredditPosts; +import me.edgan.redditslide.Constants; +import me.edgan.redditslide.Fragments.SubmissionsView; +import me.edgan.redditslide.R; +import me.edgan.redditslide.Reddit; +import me.edgan.redditslide.SettingValues; +import me.edgan.redditslide.Visuals.ColorPreferences; +import me.edgan.redditslide.Visuals.Palette; +import me.edgan.redditslide.util.LogUtil; +import me.edgan.redditslide.util.StringUtil; + +public class MainPagerAdapter extends FragmentStatePagerAdapter { + protected SubmissionsView mCurrentFragment; + private MainActivity mainActivity; + + // Modified constructor to accept MainActivity + public MainPagerAdapter(MainActivity mainActivity, FragmentManager fm) { + super(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT); + this.mainActivity = mainActivity; + + mainActivity.pager.clearOnPageChangeListeners(); + mainActivity.pager.addOnPageChangeListener( + new ViewPager.SimpleOnPageChangeListener() { + @Override + public void onPageScrolled( + int position, float positionOffset, int positionOffsetPixels) { + if (positionOffset == 0) { + mainActivity.header.animate() + .translationY(0) + .setInterpolator(new LinearInterpolator()) + .setDuration(180); + if (position < mainActivity.usedArray.size()) { + mainActivity.doSubSidebarNoLoad(mainActivity.usedArray.get(position)); + } + } + } + + @Override + public void onPageSelected(final int position) { + if (position >= mainActivity.usedArray.size()) return; + + Reddit.currentPosition = position; + mainActivity.selectedSub = mainActivity.usedArray.get(position); + SubmissionsView page = (SubmissionsView) getCurrentFragment(); + + if (mainActivity.hea != null) { + mainActivity.hea.setBackgroundColor(Palette.getColor(mainActivity.selectedSub)); + if (mainActivity.accountsArea != null) { + mainActivity.accountsArea.setBackgroundColor( + Palette.getDarkerColor(mainActivity.selectedSub)); + } + } + + int colorFrom = ((ColorDrawable) mainActivity.header.getBackground()).getColor(); + int colorTo = Palette.getColor(mainActivity.selectedSub); + + ValueAnimator colorAnimation = + ValueAnimator.ofObject(new ArgbEvaluator(), colorFrom, colorTo); + + colorAnimation.addUpdateListener( + new ValueAnimator.AnimatorUpdateListener() { + @Override + public void onAnimationUpdate(ValueAnimator animator) { + int color = (int) animator.getAnimatedValue(); + + mainActivity.header.setBackgroundColor(color); + + if (Build.VERSION.SDK_INT + >= Build.VERSION_CODES.LOLLIPOP) { + int finalColor = Palette.getDarkerColor(color); + + if (SettingValues.alwaysBlackStatusbar) { + finalColor = android.graphics.Color.BLACK; + } + + mainActivity.getWindow().setStatusBarColor(finalColor); + + if (SettingValues.colorNavBar) { + mainActivity.getWindow() + .setNavigationBarColor( + Palette.getDarkerColor(color)); + } + } + } + }); + colorAnimation.setInterpolator(new AccelerateDecelerateInterpolator()); + colorAnimation.setDuration(200); + colorAnimation.start(); + + mainActivity.setRecentBar(mainActivity.selectedSub); + + if (SettingValues.single || mainActivity.mTabLayout == null) { + // Smooth out the fading animation for the toolbar subreddit search UI + if ((SettingValues.subredditSearchMethod + == Constants.SUBREDDIT_SEARCH_METHOD_TOOLBAR + || SettingValues.subredditSearchMethod + == Constants.SUBREDDIT_SEARCH_METHOD_BOTH) + && mainActivity.findViewById(R.id.toolbar_search).getVisibility() + == View.VISIBLE) { + new Handler() + .postDelayed( + new Runnable() { + @Override + public void run() { + mainActivity.getSupportActionBar() + .setTitle(mainActivity.selectedSub); + } + }, + mainActivity.ANIMATE_DURATION + mainActivity.ANIMATE_DURATION_OFFSET); + } else { + mainActivity.getSupportActionBar().setTitle(mainActivity.selectedSub); + } + } else { + mainActivity.mTabLayout.setSelectedTabIndicatorColor( + new ColorPreferences(mainActivity) + .getColor(mainActivity.selectedSub)); + } + if (page != null && page.adapter != null) { + SubredditPosts p = page.adapter.dataSet; + if (p.offline && !mainActivity.isRestart) { + p.doMainActivityOffline(mainActivity, p.displayer); + } + } + } + }); + + if (mainActivity.pager.getAdapter() != null) { + mainActivity.pager.getAdapter().notifyDataSetChanged(); + mainActivity.pager.setCurrentItem(1); + mainActivity.pager.setCurrentItem(0); + } + } + + @Override + public int getCount() { + if (mainActivity.usedArray == null) { + return 1; + } else { + return mainActivity.usedArray.size(); + } + } + + @NonNull + @Override + public Fragment getItem(int i) { + SubmissionsView f = new SubmissionsView(); + Bundle args = new Bundle(); + String name; + if (i < mainActivity.usedArray.size()) { + if (mainActivity.multiNameToSubsMap.containsKey(mainActivity.usedArray.get(i))) { + name = mainActivity.multiNameToSubsMap.get(mainActivity.usedArray.get(i)); + } else { + name = mainActivity.usedArray.get(i); + } + args.putString("id", name); + } else { + args.putString("id", "frontpage"); + } + f.setArguments(args); + + return f; + } + + @Override + public void setPrimaryItem( + @NonNull ViewGroup container, int position, @NonNull Object object) { + // Ensure position is valid before accessing usedArray + if (position >= 0 && position < mainActivity.usedArray.size()) { + if (mainActivity.reloadItemNumber == position || mainActivity.reloadItemNumber < 0) { + super.setPrimaryItem(container, position, object); + // Check size again before calling doSetPrimary + if (position < mainActivity.usedArray.size()) { + doSetPrimary(object, position); + } + } else { + // Ensure reloadItemNumber is valid + if (mainActivity.reloadItemNumber >= 0 && mainActivity.reloadItemNumber < mainActivity.usedArray.size()) { + if (mainActivity.multiNameToSubsMap.containsKey(mainActivity.usedArray.get(mainActivity.reloadItemNumber))) { + mainActivity.shouldLoad = mainActivity.multiNameToSubsMap.get(mainActivity.usedArray.get(mainActivity.reloadItemNumber)); + } else { + mainActivity.shouldLoad = mainActivity.usedArray.get(mainActivity.reloadItemNumber); + } + } else { + mainActivity.shouldLoad = "frontpage"; + } + } + } else { + // Handle invalid position, maybe log an error or do nothing + Log.e(LogUtil.getTag(), "Invalid position in setPrimaryItem: " + position); + } + } + + + @Override + public Parcelable saveState() { + return null; + } + + public void doSetPrimary(Object object, int position) { + // Add null check for usedArray and bounds check for position + if (mainActivity.usedArray == null || position < 0 || position >= mainActivity.usedArray.size()) { + Log.e(LogUtil.getTag(), "Invalid state in doSetPrimary: usedArray=" + mainActivity.usedArray + ", position=" + position); + return; + } + + if (object != null + && getCurrentFragment() != object + && position != mainActivity.toOpenComments + && object instanceof SubmissionsView) { + mainActivity.shouldLoad = mainActivity.usedArray.get(position); + if (mainActivity.multiNameToSubsMap.containsKey(mainActivity.usedArray.get(position))) { + mainActivity.shouldLoad = mainActivity.multiNameToSubsMap.get(mainActivity.usedArray.get(position)); + } else { + mainActivity.shouldLoad = mainActivity.usedArray.get(position); + } + + mCurrentFragment = ((SubmissionsView) object); + if (mCurrentFragment.posts == null && mCurrentFragment.isAdded()) { + mCurrentFragment.doAdapter(); + } + } + } + + // Public getter for mCurrentFragment + public Fragment getCurrentFragment() { + return mCurrentFragment; + } + + @Override + public CharSequence getPageTitle(int position) { + if (mainActivity.usedArray != null && position < mainActivity.usedArray.size()) { + return StringUtil.abbreviate(mainActivity.usedArray.get(position), 25); + } else { + return ""; + } + } +} \ No newline at end of file From a80c4fe51f28f07bc9416c4ddd803af85f37926e Mon Sep 17 00:00:00 2001 From: Nathan Grennan Date: Sun, 30 Mar 2025 16:33:33 -0700 Subject: [PATCH 02/99] Broke out AsyncNotificationBadge class from MainActivity.java --- .../Activities/AsyncNotificationBadge.java | 186 ++++++++++++++++++ .../redditslide/Activities/MainActivity.java | 160 +-------------- 2 files changed, 190 insertions(+), 156 deletions(-) create mode 100644 app/src/main/java/me/edgan/redditslide/Activities/AsyncNotificationBadge.java diff --git a/app/src/main/java/me/edgan/redditslide/Activities/AsyncNotificationBadge.java b/app/src/main/java/me/edgan/redditslide/Activities/AsyncNotificationBadge.java new file mode 100644 index 000000000..cb0a4cd7e --- /dev/null +++ b/app/src/main/java/me/edgan/redditslide/Activities/AsyncNotificationBadge.java @@ -0,0 +1,186 @@ +package me.edgan.redditslide.Activities; + +import android.app.NotificationManager; +import android.content.Intent; +import android.os.AsyncTask; +import android.util.Log; +import android.view.View; +import android.widget.RelativeLayout; +import android.widget.TextView; + +import androidx.core.content.ContextCompat; + +import com.google.android.material.snackbar.Snackbar; + +import java.util.HashSet; +import java.util.Locale; +import java.util.Set; + +import me.edgan.redditslide.Authentication; +import me.edgan.redditslide.R; +import me.edgan.redditslide.Reddit; +import me.edgan.redditslide.UserSubscriptions; +import me.edgan.redditslide.Autocache.AutoCacheScheduler; +import me.edgan.redditslide.Notifications.NotificationJobScheduler; +import me.edgan.redditslide.util.LayoutUtils; +import me.edgan.redditslide.util.LogUtil; +import me.edgan.redditslide.util.OnSingleClickListener; +import net.dean.jraw.models.LoggedInAccount; + +import static me.edgan.redditslide.UserSubscriptions.modOf; + +public class AsyncNotificationBadge extends AsyncTask { + private MainActivity activity; + int count; + boolean restart; + + public AsyncNotificationBadge(MainActivity activity) { + this.activity = activity; + } + + @Override + protected Void doInBackground(Void... params) { + try { + LoggedInAccount me; + if (Authentication.me == null) { + Authentication.me = Authentication.reddit.me(); + me = Authentication.me; + if (Authentication.name.equalsIgnoreCase("loggedout")) { + Authentication.name = me.getFullName(); + Reddit.appRestart.edit().putString("name", Authentication.name).apply(); + restart = true; + return null; + } + Authentication.mod = me.isMod(); + + Authentication.authentication + .edit() + .putBoolean(Reddit.SHARED_PREF_IS_MOD, Authentication.mod) + .apply(); + + if (Reddit.notificationTime != -1) { + Reddit.notifications = new NotificationJobScheduler(activity); + Reddit.notifications.start(); + } + if (Reddit.cachedData.contains("toCache")) { + Reddit.autoCache = new AutoCacheScheduler(activity); + Reddit.autoCache.start(); + } + final String name = me.getFullName(); + Authentication.name = name; + LogUtil.v("AUTHENTICATED"); + if (Authentication.reddit.isAuthenticated()) { + final Set accounts = + Authentication.authentication.getStringSet( + "accounts", new HashSet()); + if (accounts.contains(name)) { + accounts.remove(name); + accounts.add(name + ":" + Authentication.refresh); + Authentication.authentication + .edit() + .putStringSet("accounts", accounts) + .commit(); // force commit + } + Authentication.isLoggedIn = true; + Reddit.notFirst = true; + } + } else { + me = Authentication.reddit.me(); + } + count = me.getInboxCount(); // Force reload of the LoggedInAccount object + UserSubscriptions.doFriendsOfMain(activity); + + } catch (Exception e) { + Log.w(LogUtil.getTag(), "Cannot fetch inbox count"); + count = -1; + } + + return null; + } + + @Override + protected void onPostExecute(Void aVoid) { + if (restart) { + activity.restartTheme(); + return; + } + // Ensure headerMain is not null before accessing its children + if (activity.headerMain == null) { + Log.e(LogUtil.getTag(), "headerMain is null in AsyncNotificationBadge.onPostExecute"); + return; // Cannot proceed without headerMain + } + + if (Authentication.mod && Authentication.didOnline) { + RelativeLayout mod = activity.headerMain.findViewById(R.id.mod); + if (mod != null) { + mod.setVisibility(View.VISIBLE); + + mod.setOnClickListener( + new OnSingleClickListener() { + @Override + public void onSingleClick(View view) { + if (modOf != null && !modOf.isEmpty()) { + Intent inte = new Intent(activity, ModQueue.class); + activity.startActivity(inte); + } + } + }); + } else { + Log.e(LogUtil.getTag(), "R.id.mod not found in headerMain"); + } + } + if (count != -1) { + int oldCount = Reddit.appRestart.getInt("inbox", 0); + if (count > oldCount) { + // Ensure mToolbar is not null before using it + if (activity.mToolbar == null) { + Log.e(LogUtil.getTag(), "mToolbar is null in AsyncNotificationBadge.onPostExecute"); + } else { + final Snackbar s = + Snackbar.make( + activity.mToolbar, + activity.getResources() + .getQuantityString( + R.plurals.new_messages, + count - oldCount, + count - oldCount), + Snackbar.LENGTH_LONG) + .setAction( + R.string.btn_view, + new OnSingleClickListener() { + @Override + public void onSingleClick(View v) { + Intent i = new Intent(activity, Inbox.class); + i.putExtra(Inbox.EXTRA_UNREAD, true); + activity.startActivity(i); + } + }); + + LayoutUtils.showSnackbar(s); + } + } + Reddit.appRestart.edit().putInt("inbox", count).apply(); + } + View badge = activity.headerMain.findViewById(R.id.count); + if (badge != null) { // Check if findViewById returned a valid view + if (count == 0) { + badge.setVisibility(View.GONE); + NotificationManager notificationManager = + ContextCompat.getSystemService(activity, NotificationManager.class); + if (notificationManager != null) { + notificationManager.cancel(0); + } + } else if (count != -1) { + badge.setVisibility(View.VISIBLE); + TextView countTextView = activity.headerMain.findViewById(R.id.count); + if (countTextView != null) { + countTextView.setText(String.format(Locale.getDefault(), "%d", count)); + } else { + Log.e(LogUtil.getTag(), "R.id.count (TextView) not found in headerMain"); + } + } + } else { + Log.e(LogUtil.getTag(), "R.id.count (View) not found in headerMain"); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/me/edgan/redditslide/Activities/MainActivity.java b/app/src/main/java/me/edgan/redditslide/Activities/MainActivity.java index 69e84b6f0..ddc4ba7c7 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/MainActivity.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/MainActivity.java @@ -7,7 +7,6 @@ import android.animation.ValueAnimator; import android.app.Activity; import android.app.Dialog; -import android.app.NotificationManager; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; @@ -58,7 +57,6 @@ import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ListView; -import android.widget.RelativeLayout; import android.widget.TextView; import android.widget.Toast; @@ -72,7 +70,6 @@ import androidx.appcompat.widget.SwitchCompat; import androidx.appcompat.widget.Toolbar; import androidx.cardview.widget.CardView; -import androidx.core.content.ContextCompat; import androidx.core.content.pm.ShortcutInfoCompat; import androidx.core.content.pm.ShortcutManagerCompat; import androidx.core.content.res.ResourcesCompat; @@ -99,7 +96,6 @@ import me.edgan.redditslide.Adapters.SideArrayAdapter; import me.edgan.redditslide.Adapters.SubredditPosts; import me.edgan.redditslide.Authentication; -import me.edgan.redditslide.Autocache.AutoCacheScheduler; import me.edgan.redditslide.BuildConfig; import me.edgan.redditslide.CaseInsensitiveArrayList; import me.edgan.redditslide.CommentCacheAsync; @@ -110,7 +106,6 @@ import me.edgan.redditslide.Fragments.SubmissionsView; import me.edgan.redditslide.ImageFlairs; import me.edgan.redditslide.Notifications.CheckForMail; -import me.edgan.redditslide.Notifications.NotificationJobScheduler; import me.edgan.redditslide.R; import me.edgan.redditslide.Reddit; import me.edgan.redditslide.SettingValues; @@ -155,7 +150,6 @@ import net.dean.jraw.managers.ModerationManager; import net.dean.jraw.managers.MultiRedditManager; import net.dean.jraw.models.FlairTemplate; -import net.dean.jraw.models.LoggedInAccount; import net.dean.jraw.models.MultiReddit; import net.dean.jraw.models.MultiSubreddit; import net.dean.jraw.models.Submission; @@ -310,7 +304,7 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) { UserSubscriptions.doMainActivitySubs(this); } else if (requestCode == INBOX_RESULT) { // update notification badge - new AsyncNotificationBadge().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + new AsyncNotificationBadge(this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } else if (requestCode == 3333) { this.data = data; if (doImage != null) { @@ -840,7 +834,7 @@ protected void onCreate(final Bundle savedInstanceState) { public void run() { runAfterLoad = null; if (Authentication.isLoggedIn) { - new AsyncNotificationBadge() + new AsyncNotificationBadge(MainActivity.this) .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } if (!Reddit.appRestart @@ -1171,7 +1165,7 @@ public void onResume() { && NetworkUtil.isConnected(MainActivity.this) && headerMain != null && runAfterLoad == null) { - new AsyncNotificationBadge().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + new AsyncNotificationBadge(this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } else if (Authentication.isLoggedIn && Authentication.name.equalsIgnoreCase("loggedout")) { restartTheme(); // force a restart because we should not be here } @@ -1708,7 +1702,7 @@ public void onSingleClick(View view) { headerMain = header; if (runAfterLoad == null) { - new AsyncNotificationBadge().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + new AsyncNotificationBadge(this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } } else if (Authentication.didOnline) { @@ -4846,152 +4840,6 @@ protected Subreddit doInBackground(String... params) { } } - public class AsyncNotificationBadge extends AsyncTask { - int count; - - boolean restart; - int modCount; - - @Override - protected Void doInBackground(Void... params) { - try { - LoggedInAccount me; - if (Authentication.me == null) { - Authentication.me = Authentication.reddit.me(); - me = Authentication.me; - if (Authentication.name.equalsIgnoreCase("loggedout")) { - Authentication.name = me.getFullName(); - Reddit.appRestart.edit().putString("name", Authentication.name).apply(); - restart = true; - return null; - } - Authentication.mod = me.isMod(); - - Authentication.authentication - .edit() - .putBoolean(Reddit.SHARED_PREF_IS_MOD, Authentication.mod) - .apply(); - - if (Reddit.notificationTime != -1) { - Reddit.notifications = new NotificationJobScheduler(MainActivity.this); - Reddit.notifications.start(); - } - if (Reddit.cachedData.contains("toCache")) { - Reddit.autoCache = new AutoCacheScheduler(MainActivity.this); - Reddit.autoCache.start(); - } - final String name = me.getFullName(); - Authentication.name = name; - LogUtil.v("AUTHENTICATED"); - if (Authentication.reddit.isAuthenticated()) { - final Set accounts = - Authentication.authentication.getStringSet( - "accounts", new HashSet()); - if (accounts.contains(name)) { // convert to new system - accounts.remove(name); - accounts.add(name + ":" + Authentication.refresh); - Authentication.authentication - .edit() - .putStringSet("accounts", accounts) - .commit(); // force commit - } - Authentication.isLoggedIn = true; - Reddit.notFirst = true; - } - } else { - me = Authentication.reddit.me(); - } - count = me.getInboxCount(); // Force reload of the LoggedInAccount object - UserSubscriptions.doFriendsOfMain(MainActivity.this); - - } catch (Exception e) { - Log.w(LogUtil.getTag(), "Cannot fetch inbox count"); - count = -1; - } - - return null; - } - - @Override - protected void onPostExecute(Void aVoid) { - if (restart) { - restartTheme(); - return; - } - if (Authentication.mod && Authentication.didOnline) { - RelativeLayout mod = headerMain.findViewById(R.id.mod); - mod.setVisibility(View.VISIBLE); - - mod.setOnClickListener( - new OnSingleClickListener() { - @Override - public void onSingleClick(View view) { - if (modOf != null && !modOf.isEmpty()) { - Intent inte = new Intent(MainActivity.this, ModQueue.class); - MainActivity.this.startActivity(inte); - } - } - }); - } - if (count != -1) { - int oldCount = Reddit.appRestart.getInt("inbox", 0); - if (count > oldCount) { - final Snackbar s = - Snackbar.make( - mToolbar, - getResources() - .getQuantityString( - R.plurals.new_messages, - count - oldCount, - count - oldCount), - Snackbar.LENGTH_LONG) - .setAction( - R.string.btn_view, - new OnSingleClickListener() { - @Override - public void onSingleClick(View v) { - Intent i = - new Intent( - MainActivity.this, Inbox.class); - i.putExtra(Inbox.EXTRA_UNREAD, true); - startActivity(i); - } - }); - - LayoutUtils.showSnackbar(s); - } - Reddit.appRestart.edit().putInt("inbox", count).apply(); - } - View badge = headerMain.findViewById(R.id.count); - if (count == 0) { - if (badge != null) { - badge.setVisibility(View.GONE); - } - NotificationManager notificationManager = - ContextCompat.getSystemService( - MainActivity.this, NotificationManager.class); - if (notificationManager != null) { - notificationManager.cancel(0); - } - } else if (count != -1) { - if (badge != null) { - badge.setVisibility(View.VISIBLE); - } - ((TextView) headerMain.findViewById(R.id.count)) - .setText(String.format(Locale.getDefault(), "%d", count)); - } - - /* Todo possibly - View modBadge = headerMain.findViewById(R.id.count_mod); - - if (modCount == 0) { - if (modBadge != null) modBadge.setVisibility(View.GONE); - } else if (modCount != -1) { - if (modBadge != null) modBadge.setVisibility(View.VISIBLE); - ((TextView) headerMain.findViewById(R.id.count)).setText(String.format(Locale.getDefault(), "%d", count)); - }*/ - } - } public class MainPagerAdapterComment extends MainPagerAdapter { From 8539fd8413399d137343aa8a4761f90df2be21e5 Mon Sep 17 00:00:00 2001 From: Nathan Grennan Date: Sun, 30 Mar 2025 16:38:16 -0700 Subject: [PATCH 03/99] Broke AsyncGetSubredditTask class out of MainActivity.java --- .../Activities/AsyncGetSubredditTask.java | 53 +++++++++++++++++++ .../redditslide/Activities/MainActivity.java | 23 +------- 2 files changed, 55 insertions(+), 21 deletions(-) create mode 100644 app/src/main/java/me/edgan/redditslide/Activities/AsyncGetSubredditTask.java diff --git a/app/src/main/java/me/edgan/redditslide/Activities/AsyncGetSubredditTask.java b/app/src/main/java/me/edgan/redditslide/Activities/AsyncGetSubredditTask.java new file mode 100644 index 000000000..89dfe8d00 --- /dev/null +++ b/app/src/main/java/me/edgan/redditslide/Activities/AsyncGetSubredditTask.java @@ -0,0 +1,53 @@ +package me.edgan.redditslide.Activities; + +import android.os.AsyncTask; +import android.util.Log; + +import me.edgan.redditslide.Authentication; +import me.edgan.redditslide.util.LogUtil; +import net.dean.jraw.models.Subreddit; + +/** + * AsyncTask to fetch Subreddit details asynchronously. + */ +public class AsyncGetSubredditTask extends AsyncTask { + + private MainActivity mainActivity; + + public AsyncGetSubredditTask(MainActivity activity) { + this.mainActivity = activity; + } + + @Override + public void onPostExecute(Subreddit subreddit) { + // Ensure mainActivity is still valid before calling its method + if (mainActivity != null && !mainActivity.isDestroyed() && subreddit != null) { + mainActivity.doSubOnlyStuff(subreddit); + } else if (mainActivity != null && !mainActivity.isDestroyed()) { + // Handle cases where subreddit is null (e.g., network error, subreddit not found) + Log.w(LogUtil.getTag(), "Failed to fetch subreddit details or subreddit is null."); + // Optionally, inform the user or update UI accordingly in MainActivity + // mainActivity.handleSubredditFetchError(); + } + } + + @Override + protected Subreddit doInBackground(String... params) { + if (params == null || params.length == 0 || params[0] == null || params[0].isEmpty()) { + Log.e(LogUtil.getTag(), "Subreddit name parameter is missing or empty."); + return null; + } + String subredditName = params[0]; + try { + // Ensure Authentication.reddit is initialized + if (Authentication.reddit == null) { + Log.e(LogUtil.getTag(), "Authentication.reddit is not initialized."); + return null; + } + return Authentication.reddit.getSubreddit(subredditName); + } catch (Exception e) { + Log.e(LogUtil.getTag(), "Error fetching subreddit: " + subredditName, e); + return null; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/me/edgan/redditslide/Activities/MainActivity.java b/app/src/main/java/me/edgan/redditslide/Activities/MainActivity.java index ddc4ba7c7..805e929eb 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/MainActivity.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/MainActivity.java @@ -232,7 +232,7 @@ public class MainActivity extends BaseActivity AsyncTask caching; boolean currentlySubbed; int back; - private AsyncGetSubreddit mAsyncGetSubreddit = null; + private AsyncGetSubredditTask mAsyncGetSubreddit = null; private int headerHeight; // height of the header public int reloadItemNumber = -2; private static final int NOTIFICATION_PERMISSION_REQUEST_CODE = 1001; @@ -2936,7 +2936,7 @@ public void doSubSidebar(final String subreddit) { drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, GravityCompat.END); } - mAsyncGetSubreddit = new AsyncGetSubreddit(); + mAsyncGetSubreddit = new AsyncGetSubredditTask(this); mAsyncGetSubreddit.execute(subreddit); final View dialoglayout = findViewById(R.id.sidebarsub); @@ -4823,25 +4823,6 @@ public void afterTextChanged( } } - public class AsyncGetSubreddit extends AsyncTask { - - @Override - public void onPostExecute(Subreddit subreddit) { - if (subreddit != null) doSubOnlyStuff(subreddit); - } - - @Override - protected Subreddit doInBackground(String... params) { - try { - return Authentication.reddit.getSubreddit(params[0]); - } catch (Exception e) { - return null; - } - } - } - - - public class MainPagerAdapterComment extends MainPagerAdapter { public int size = usedArray.size(); public Fragment storedFragment; From 11f9eaa7e55dc2740c2b72c0abd94616f9a90b6b Mon Sep 17 00:00:00 2001 From: Nathan Grennan Date: Sun, 30 Mar 2025 19:23:46 -0700 Subject: [PATCH 04/99] Broke DrawerController and MainPagerAdapterComment classes out of MainActivity.java --- .../Activities/DrawerController.java | 1265 +++++++++++++++ .../redditslide/Activities/MainActivity.java | 1388 +---------------- .../Activities/MainPagerAdapterComment.java | 190 +++ .../Adapters/SideArrayAdapter.java | 5 +- .../Adapters/SubmissionAdapter.java | 7 +- .../Adapters/SubmissionNewsAdapter.java | 7 +- 6 files changed, 1475 insertions(+), 1387 deletions(-) create mode 100644 app/src/main/java/me/edgan/redditslide/Activities/DrawerController.java create mode 100644 app/src/main/java/me/edgan/redditslide/Activities/MainPagerAdapterComment.java diff --git a/app/src/main/java/me/edgan/redditslide/Activities/DrawerController.java b/app/src/main/java/me/edgan/redditslide/Activities/DrawerController.java new file mode 100644 index 000000000..6152dd07a --- /dev/null +++ b/app/src/main/java/me/edgan/redditslide/Activities/DrawerController.java @@ -0,0 +1,1265 @@ +package me.edgan.redditslide.Activities; + +import android.animation.Animator; +import android.animation.AnimatorListenerAdapter; +import android.animation.ValueAnimator; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.os.AsyncTask; +import android.text.Editable; +import android.text.InputType; +import android.text.TextWatcher; +import android.util.TypedValue; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.View; +import android.view.WindowManager; +import android.view.inputmethod.EditorInfo; +import android.widget.Button; +import android.widget.CompoundButton; +import android.widget.EditText; +import android.widget.FrameLayout; +import android.widget.LinearLayout; +import android.widget.ListView; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.view.ContextThemeWrapper; +import androidx.appcompat.widget.SwitchCompat; +import androidx.appcompat.widget.Toolbar; +import androidx.core.view.GravityCompat; + +import com.afollestad.materialdialogs.DialogAction; +import com.afollestad.materialdialogs.MaterialDialog; +import com.google.android.material.dialog.MaterialAlertDialogBuilder; +import com.google.android.material.textfield.TextInputLayout; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Locale; +import java.util.Map; +import java.util.Set; + +import me.edgan.redditslide.Adapters.SideArrayAdapter; +import me.edgan.redditslide.Authentication; +import me.edgan.redditslide.Constants; +import me.edgan.redditslide.Fragments.DrawerItemsDialog; +import me.edgan.redditslide.Fragments.SubmissionsView; +import me.edgan.redditslide.R; +import me.edgan.redditslide.Reddit; +import me.edgan.redditslide.SettingValues; +import me.edgan.redditslide.UserSubscriptions; +import me.edgan.redditslide.Visuals.ColorPreferences; +import me.edgan.redditslide.Visuals.Palette; +import me.edgan.redditslide.ui.settings.ManageOfflineContent; +import me.edgan.redditslide.ui.settings.SettingsActivity; +import me.edgan.redditslide.util.AnimatorUtil; +import me.edgan.redditslide.util.EditTextValidator; +import me.edgan.redditslide.util.KeyboardUtil; +import me.edgan.redditslide.util.LogUtil; +import me.edgan.redditslide.util.NetworkUtil; +import me.edgan.redditslide.util.OnSingleClickListener; +import me.edgan.redditslide.util.stubs.SimpleTextWatcher; + +public class DrawerController { + + private final MainActivity mainActivity; + ListView drawerSubList; + EditText drawerSearch; + View hea; + View accountsArea; + SideArrayAdapter sideArrayAdapter; + HashMap accounts = new HashMap<>(); + + + public DrawerController(MainActivity mainActivity) { + this.mainActivity = mainActivity; + } + + public void doDrawer() { + drawerSubList = (ListView) mainActivity.findViewById(R.id.drawerlistview); + drawerSubList.setDividerHeight(0); + drawerSubList.setDescendantFocusability(ListView.FOCUS_BEFORE_DESCENDANTS); + final LayoutInflater inflater = mainActivity.getLayoutInflater(); + final View header; + + if (Authentication.isLoggedIn && Authentication.didOnline) { + + header = inflater.inflate(R.layout.drawer_loggedin, drawerSubList, false); + mainActivity.headerMain = header; + hea = header.findViewById(R.id.back); + + drawerSubList.addHeaderView(header, null, false); + ((TextView) header.findViewById(R.id.name)).setText(Authentication.name); + header.findViewById(R.id.multi) + .setOnClickListener( + new OnSingleClickListener() { + @Override + public void onSingleClick(View view) { + if (mainActivity.runAfterLoad == null) { + Intent inte = + new Intent( + mainActivity, + MultiredditOverview.class); + mainActivity.startActivity(inte); + } + } + }); + header.findViewById(R.id.multi).setOnLongClickListener(new View.OnLongClickListener() { + @Override + public boolean onLongClick(View view) { + showUsernameDialog(true); + return true; + } + }); + + header.findViewById(R.id.discover) + .setOnClickListener( + new OnSingleClickListener() { + @Override + public void onSingleClick(View view) { + Intent inte = new Intent(mainActivity, Discover.class); + mainActivity.startActivity(inte); + } + }); + + header.findViewById(R.id.prof_click) + .setOnClickListener( + new OnSingleClickListener() { + @Override + public void onSingleClick(View view) { + Intent inte = new Intent(mainActivity, Profile.class); + inte.putExtra(Profile.EXTRA_PROFILE, Authentication.name); + mainActivity.startActivity(inte); + } + }); + header.findViewById(R.id.saved) + .setOnClickListener( + new OnSingleClickListener() { + @Override + public void onSingleClick(View view) { + Intent inte = new Intent(mainActivity, Profile.class); + inte.putExtra(Profile.EXTRA_PROFILE, Authentication.name); + inte.putExtra(Profile.EXTRA_SAVED, true); + mainActivity.startActivity(inte); + } + }); + header.findViewById(R.id.later) + .setOnClickListener( + new OnSingleClickListener() { + @Override + public void onSingleClick(View view) { + Intent inte = + new Intent(mainActivity, PostReadLater.class); + mainActivity.startActivity(inte); + } + }); + header.findViewById(R.id.history) + .setOnClickListener( + new OnSingleClickListener() { + @Override + public void onSingleClick(View view) { + Intent inte = new Intent(mainActivity, Profile.class); + inte.putExtra(Profile.EXTRA_PROFILE, Authentication.name); + inte.putExtra(Profile.EXTRA_HISTORY, true); + mainActivity.startActivity(inte); + } + }); + header.findViewById(R.id.commented) + .setOnClickListener( + new OnSingleClickListener() { + @Override + public void onSingleClick(View view) { + Intent inte = new Intent(mainActivity, Profile.class); + inte.putExtra(Profile.EXTRA_PROFILE, Authentication.name); + inte.putExtra(Profile.EXTRA_COMMENT, true); + mainActivity.startActivity(inte); + } + }); + header.findViewById(R.id.submitted) + .setOnClickListener( + new OnSingleClickListener() { + @Override + public void onSingleClick(View view) { + Intent inte = new Intent(mainActivity, Profile.class); + inte.putExtra(Profile.EXTRA_PROFILE, Authentication.name); + inte.putExtra(Profile.EXTRA_SUBMIT, true); + mainActivity.startActivity(inte); + } + }); + header.findViewById(R.id.upvoted) + .setOnClickListener( + new OnSingleClickListener() { + @Override + public void onSingleClick(View view) { + Intent inte = new Intent(mainActivity, Profile.class); + inte.putExtra(Profile.EXTRA_PROFILE, Authentication.name); + inte.putExtra(Profile.EXTRA_UPVOTE, true); + mainActivity.startActivity(inte); + } + }); + + /** + * If the user is a known mod, show the "Moderation" drawer item quickly to stop the UI + * from jumping + */ + if (UserSubscriptions.modOf != null && !UserSubscriptions.modOf.isEmpty() && Authentication.mod) { + header.findViewById(R.id.mod).setVisibility(View.VISIBLE); + } + + // update notification badge + final LinearLayout profStuff = header.findViewById(R.id.accountsarea); + profStuff.setVisibility(View.GONE); + mainActivity.findViewById(R.id.back) + .setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View v) { + if (profStuff.getVisibility() == View.GONE) { + expand(profStuff); + header.setContentDescription( + mainActivity.getResources().getString(R.string.btn_collapse)); + AnimatorUtil.flipAnimator( + false, header.findViewById(R.id.headerflip)) + .start(); + } else { + collapse(profStuff); + header.setContentDescription( + mainActivity.getResources().getString(R.string.btn_expand)); + AnimatorUtil.flipAnimator( + true, header.findViewById(R.id.headerflip)) + .start(); + } + } + }); + + for (String s : + Authentication.authentication.getStringSet("accounts", new HashSet())) { + if (s.contains(":")) { + accounts.put(s.split(":")[0], s.split(":")[1]); + } else { + accounts.put(s, ""); + } + } + final ArrayList keys = new ArrayList<>(accounts.keySet()); + + final LinearLayout accountList = header.findViewById(R.id.accountsarea); + for (final String accName : keys) { + LogUtil.v(accName); + final View t = + mainActivity.getLayoutInflater() + .inflate(R.layout.account_textview_white, accountList, false); + ((TextView) t.findViewById(R.id.name)).setText(accName); + LogUtil.v("Adding click to " + ((TextView) t.findViewById(R.id.name)).getText()); + t.findViewById(R.id.remove) + .setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View v) { + + final Context contextThemeWrapper = new ContextThemeWrapper(mainActivity, + new ColorPreferences(mainActivity).getFontStyle().getBaseId()); + + new MaterialAlertDialogBuilder(contextThemeWrapper) + .setTitle(R.string.profile_remove) + .setMessage(R.string.profile_remove_account) + .setNegativeButton( + R.string.btn_delete, + (dialog2, which2) -> { + Set accounts2 = + Authentication.authentication + .getStringSet( + "accounts", + new HashSet<>()); + Set done = new HashSet<>(); + for (String s : accounts2) { + if (!s.contains(accName)) { + done.add(s); + } + } + Authentication.authentication + .edit() + .putStringSet("accounts", done) + .commit(); + dialog2.dismiss(); + accountList.removeView(t); + if (accName.equalsIgnoreCase( + Authentication.name)) { + + boolean d = false; + for (String s : keys) { + + if (!s.equalsIgnoreCase( + accName)) { + d = true; + LogUtil.v( + "Switching to " + + s); + for (Map.Entry< + String, + String> + e : + accounts + .entrySet()) { + LogUtil.v( + e.getKey() + + ":" + + e + .getValue()); + } + if (accounts.containsKey(s) + && !accounts.get(s) + .isEmpty()) { + Authentication + .authentication + .edit() + .putString( + "lasttoken", + accounts + .get( + s)) + .remove( + "backedCreds") + .commit(); + } else { + ArrayList + tokens = + new ArrayList<>( + Authentication + .authentication + .getStringSet( + "tokens", + new HashSet<>())); + int index = + keys.indexOf(s); + if (keys.indexOf(s) + > tokens + .size()) { + index -= 1; + } + Authentication + .authentication + .edit() + .putString( + "lasttoken", + tokens + .get( + index)) + .remove( + "backedCreds") + .commit(); + } + Authentication.name = s; + UserSubscriptions + .switchAccounts(); + Reddit.forceRestart( + mainActivity, + true); + break; + } + } + if (!d) { + Authentication.name = + "LOGGEDOUT"; + Authentication.isLoggedIn = + false; + Authentication.authentication + .edit() + .remove("lasttoken") + .remove("backedCreds") + .commit(); + UserSubscriptions + .switchAccounts(); + Reddit.forceRestart( + mainActivity, + true); + } + + } else { + accounts.remove(accName); + keys.remove(accName); + } + }) + .setPositiveButton(R.string.btn_cancel, null) + .show(); + } + }); + t.setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View v) { + String accName = + ((TextView) t.findViewById(R.id.name)).getText().toString(); + LogUtil.v("Found name is " + accName); + if (!accName.equalsIgnoreCase(Authentication.name)) { + LogUtil.v("Switching to " + accName); + if (!accounts.get(accName).isEmpty()) { + LogUtil.v("Using token " + accounts.get(accName)); + Authentication.authentication + .edit() + .putString("lasttoken", accounts.get(accName)) + .remove("backedCreds") + .apply(); + } else { + ArrayList tokens = + new ArrayList<>( + Authentication.authentication.getStringSet( + "tokens", new HashSet())); + Authentication.authentication + .edit() + .putString( + "lasttoken", + tokens.get(keys.indexOf(accName))) + .remove("backedCreds") + .apply(); + } + Authentication.name = accName; + + UserSubscriptions.switchAccounts(); + + Reddit.forceRestart(mainActivity, true); + } + } + }); + accountList.addView(t); + } + + header.findViewById(R.id.godown) + .setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View view) { + LinearLayout body = header.findViewById(R.id.expand_profile); + if (body.getVisibility() == View.GONE) { + expand(body); + AnimatorUtil.flipAnimator(false, view).start(); + view.findViewById(R.id.godown) + .setContentDescription( + mainActivity.getResources() + .getString(R.string.btn_collapse)); + } else { + collapse(body); + AnimatorUtil.flipAnimator(true, view).start(); + view.findViewById(R.id.godown) + .setContentDescription( + mainActivity.getResources() + .getString(R.string.btn_expand)); + } + } + }); + + header.findViewById(R.id.guest_mode) + .setOnClickListener( + new OnSingleClickListener() { + @Override + public void onSingleClick(View v) { + Authentication.name = "LOGGEDOUT"; + Authentication.isLoggedIn = false; + Authentication.authentication + .edit() + .remove("lasttoken") + .remove("backedCreds") + .apply(); + UserSubscriptions.switchAccounts(); + Reddit.forceRestart(mainActivity, true); + } + }); + + header.findViewById(R.id.add) + .setOnClickListener( + new OnSingleClickListener() { + @Override + public void onSingleClick(View view) { + Intent inte = new Intent(mainActivity, Login.class); + mainActivity.startActivity(inte); + } + }); + header.findViewById(R.id.offline) + .setOnClickListener( + new OnSingleClickListener() { + @Override + public void onSingleClick(View view) { + Reddit.appRestart + .edit() + .putBoolean("forceoffline", true) + .commit(); + Reddit.forceRestart(mainActivity, false); + } + }); + header.findViewById(R.id.inbox) + .setOnClickListener( + new OnSingleClickListener() { + @Override + public void onSingleClick(View view) { + Intent inte = new Intent(mainActivity, Inbox.class); + mainActivity.startActivityForResult(inte, MainActivity.INBOX_RESULT); + } + }); + + mainActivity.headerMain = header; + + if (mainActivity.runAfterLoad == null) { + new AsyncNotificationBadge(mainActivity).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + } else if (Authentication.didOnline) { + header = inflater.inflate(R.layout.drawer_loggedout, drawerSubList, false); + drawerSubList.addHeaderView(header, null, false); + mainActivity.headerMain = header; + hea = header.findViewById(R.id.back); + + final LinearLayout profStuff = header.findViewById(R.id.accountsarea); + profStuff.setVisibility(View.GONE); + mainActivity.findViewById(R.id.back) + .setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View v) { + if (profStuff.getVisibility() == View.GONE) { + expand(profStuff); + AnimatorUtil.flipAnimator( + false, header.findViewById(R.id.headerflip)) + .start(); + header.findViewById(R.id.headerflip) + .setContentDescription( + mainActivity.getResources() + .getString(R.string.btn_collapse)); + } else { + collapse(profStuff); + AnimatorUtil.flipAnimator( + true, header.findViewById(R.id.headerflip)) + .start(); + header.findViewById(R.id.headerflip) + .setContentDescription( + mainActivity.getResources() + .getString(R.string.btn_expand)); + } + } + }); + final HashMap accounts = new HashMap<>(); + + for (String s : + Authentication.authentication.getStringSet("accounts", new HashSet())) { + if (s.contains(":")) { + accounts.put(s.split(":")[0], s.split(":")[1]); + } else { + accounts.put(s, ""); + } + } + final ArrayList keys = new ArrayList<>(accounts.keySet()); + + final LinearLayout accountList = header.findViewById(R.id.accountsarea); + for (final String accName : keys) { + LogUtil.v(accName); + final View t = + mainActivity.getLayoutInflater() + .inflate(R.layout.account_textview_white, accountList, false); + ((TextView) t.findViewById(R.id.name)).setText(accName); + t.findViewById(R.id.remove) + .setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View v) { + + new AlertDialog.Builder(mainActivity) + .setTitle(R.string.profile_remove) + .setMessage(R.string.profile_remove_account) + .setNegativeButton( + R.string.btn_delete, + (dialog2, which2) -> { + Set accounts2 = + Authentication.authentication + .getStringSet( + "accounts", + new HashSet<>()); + Set done = new HashSet<>(); + for (String s : accounts2) { + if (!s.contains(accName)) { + done.add(s); + } + } + Authentication.authentication + .edit() + .putStringSet("accounts", done) + .commit(); + dialog2.dismiss(); + accountList.removeView(t); + + if (accName.equalsIgnoreCase( + Authentication.name)) { + boolean d = false; + for (String s : keys) { + if (!s.equalsIgnoreCase( + accName)) { + d = true; + LogUtil.v( + "Switching to " + + s); + if (!accounts.get(s) + .isEmpty()) { + Authentication + .authentication + .edit() + .putString( + "lasttoken", + accounts + .get( + s)) + .remove( + "backedCreds") + .commit(); + + } else { + ArrayList + tokens = + new ArrayList<>( + Authentication + .authentication + .getStringSet( + "tokens", + new HashSet<>())); + Authentication + .authentication + .edit() + .putString( + "lasttoken", + tokens + .get( + keys + .indexOf( + s))) + .remove( + "backedCreds") + .commit(); + } + Authentication.name = s; + UserSubscriptions + .switchAccounts(); + Reddit.forceRestart( + mainActivity, + true); + } + } + if (!d) { + Authentication.name = + "LOGGEDOUT"; + Authentication.isLoggedIn = + false; + Authentication.authentication + .edit() + .remove("lasttoken") + .remove("backedCreds") + .commit(); + UserSubscriptions + .switchAccounts(); + Reddit.forceRestart( + mainActivity, + true); + } + } else { + accounts.remove(accName); + keys.remove(accName); + } + }) + .setPositiveButton(R.string.btn_cancel, null) + .show(); + } + }); + t.setOnClickListener( + new OnSingleClickListener() { + @Override + public void onSingleClick(View v) { + if (!accName.equalsIgnoreCase(Authentication.name)) { + if (!accounts.get(accName).isEmpty()) { + Authentication.authentication + .edit() + .putString("lasttoken", accounts.get(accName)) + .remove("backedCreds") + .commit(); + } else { + ArrayList tokens = + new ArrayList<>( + Authentication.authentication.getStringSet( + "tokens", new HashSet())); + Authentication.authentication + .edit() + .putString( + "lasttoken", + tokens.get(keys.indexOf(accName))) + .remove("backedCreds") + .commit(); + } + + // Removing this will break Guest mode + Authentication.isLoggedIn = true; + + Authentication.name = accName; + + UserSubscriptions.switchAccounts(); + + Reddit.forceRestart(mainActivity, true); + } + } + }); + accountList.addView(t); + } + + header.findViewById(R.id.add) + .setOnClickListener( + new OnSingleClickListener() { + @Override + public void onSingleClick(View view) { + Intent inte = new Intent(mainActivity, Login.class); + mainActivity.startActivity(inte); + } + }); + header.findViewById(R.id.offline) + .setOnClickListener( + new OnSingleClickListener() { + @Override + public void onSingleClick(View view) { + Reddit.appRestart + .edit() + .putBoolean("forceoffline", true) + .commit(); + Reddit.forceRestart(mainActivity, false); + } + }); + mainActivity.headerMain = header; + + header.findViewById(R.id.multi) + .setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View view) { + new MaterialDialog.Builder(mainActivity) + .inputRange(3, 20) + .alwaysCallInputCallback() + .input( + mainActivity.getString(R.string.user_enter), + null, + new MaterialDialog.InputCallback() { + @Override + public void onInput( + @NonNull MaterialDialog dialog, + CharSequence input) { + final EditText editText = + dialog.getInputEditText(); + EditTextValidator.validateUsername( + editText); + if (input.length() >= 3 + && input.length() <= 20) { + dialog.getActionButton( + DialogAction + .POSITIVE) + .setEnabled(true); + } + } + }) + .positiveText(R.string.user_btn_gotomultis) + .onPositive( + new MaterialDialog.SingleButtonCallback() { + @Override + public void onClick( + @NonNull MaterialDialog dialog, + @NonNull DialogAction which) { + if (mainActivity.runAfterLoad == null) { + Intent inte = + new Intent( + mainActivity, + MultiredditOverview + .class); + inte.putExtra( + Profile.EXTRA_PROFILE, + dialog.getInputEditText() + .getText() + .toString()); + mainActivity.startActivity( + inte); + } + } + }) + .negativeText(R.string.btn_cancel) + .show(); + } + }); + + } else { + header = inflater.inflate(R.layout.drawer_offline, drawerSubList, false); + mainActivity.headerMain = header; + drawerSubList.addHeaderView(header, null, false); + hea = header.findViewById(R.id.back); + + header.findViewById(R.id.online) + .setOnClickListener( + new OnSingleClickListener() { + @Override + public void onSingleClick(View view) { + Reddit.appRestart.edit().remove("forceoffline").commit(); + Reddit.forceRestart(mainActivity, false); + } + }); + } + + final LinearLayout expandSettings = header.findViewById(R.id.expand_settings); + header.findViewById(R.id.godown_settings) + .setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View v) { + if (expandSettings.getVisibility() == View.GONE) { + expand(expandSettings); + header.findViewById(R.id.godown_settings) + .setContentDescription( + mainActivity.getResources() + .getString(R.string.btn_collapse)); + AnimatorUtil.flipAnimator(false, v).start(); + } else { + collapse(expandSettings); + header.findViewById(R.id.godown_settings) + .setContentDescription( + mainActivity.getResources().getString(R.string.btn_expand)); + AnimatorUtil.flipAnimator(true, v).start(); + } + } + }); + + { // Set up quick setting toggles + final SwitchCompat toggleNightMode = + expandSettings.findViewById(R.id.toggle_night_mode); + toggleNightMode.setVisibility(View.VISIBLE); + toggleNightMode.setChecked(mainActivity.inNightMode); + toggleNightMode.setOnCheckedChangeListener( + new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + SettingValues.forcedNightModeState = + isChecked + ? SettingValues.ForcedState.FORCED_ON + : SettingValues.ForcedState.FORCED_OFF; + mainActivity.restartTheme(); + } + }); + + final SwitchCompat toggleImmersiveMode = + expandSettings.findViewById(R.id.toggle_immersive_mode); + toggleImmersiveMode.setChecked(SettingValues.immersiveMode); + toggleImmersiveMode.setOnCheckedChangeListener( + new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + SettingValues.immersiveMode = isChecked; + SettingValues.prefs + .edit() + .putBoolean(SettingValues.PREF_IMMERSIVE_MODE, isChecked) + .apply(); + if (isChecked) { + mainActivity.hideDecor(); + } else { + mainActivity.showDecor(); + } + } + }); + + final SwitchCompat toggleNSFW = expandSettings.findViewById(R.id.toggle_nsfw); + toggleNSFW.setChecked(SettingValues.showNSFWContent); + toggleNSFW.setOnCheckedChangeListener( + new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + SettingValues.showNSFWContent = isChecked; + SettingValues.prefs + .edit() + .putBoolean(SettingValues.PREF_SHOW_NSFW_CONTENT, isChecked) + .apply(); + mainActivity.reloadSubs(); + } + }); + + final SwitchCompat toggleRightThumbnails = + expandSettings.findViewById(R.id.toggle_right_thumbnails); + toggleRightThumbnails.setChecked(SettingValues.switchThumb); + toggleRightThumbnails.setOnCheckedChangeListener( + new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + SettingValues.switchThumb = isChecked; + SettingValues.prefs + .edit() + .putBoolean(SettingValues.PREF_SWITCH_THUMB, isChecked) + .apply(); + mainActivity.reloadSubs(); + } + }); + + final SwitchCompat toggleReaderMode = + expandSettings.findViewById(R.id.toggle_reader_mode); + toggleReaderMode.setChecked(SettingValues.readerMode); + toggleReaderMode.setOnCheckedChangeListener( + new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + SettingValues.readerMode = isChecked; + SettingValues.prefs + .edit() + .putBoolean(SettingValues.PREF_READER_MODE, isChecked) + .apply(); + } + }); + } + + header.findViewById(R.id.manage) + .setOnClickListener( + new OnSingleClickListener() { + @Override + public void onSingleClick(View view) { + Intent i = + new Intent(mainActivity, ManageOfflineContent.class); + mainActivity.startActivity(i); + } + }); + if (Authentication.didOnline) { + View support = header.findViewById(R.id.support); + + support.setVisibility(View.GONE); + header.findViewById(R.id.prof) + .setOnClickListener(new OnSingleClickListener() { + @Override + public void onSingleClick(View view) { + showUsernameDialog(false); // false for profile view + } + }); + + } + + header.findViewById(R.id.settings) + .setOnClickListener( + new OnSingleClickListener() { + @Override + public void onSingleClick(View v) { + Intent i = new Intent(mainActivity, SettingsActivity.class); + mainActivity.startActivity(i); + // Cancel sub loading because exiting the settings will reload it + // anyway + if (mainActivity.mAsyncGetSubreddit != null) mainActivity.mAsyncGetSubreddit.cancel(true); + mainActivity.drawerLayout.closeDrawers(); + } + }); + + final Toolbar toolbar = (Toolbar) mainActivity.findViewById(R.id.toolbar); + + final androidx.appcompat.app.ActionBarDrawerToggle actionBarDrawerToggle = + new androidx.appcompat.app.ActionBarDrawerToggle( + mainActivity, + mainActivity.drawerLayout, + toolbar, + R.string.btn_open, + R.string.btn_close) { + @Override + public void onDrawerSlide(View drawerView, float slideOffset) { + super.onDrawerSlide(drawerView, 0); // this disables the animation + } + + @Override + public void onDrawerOpened(View drawerView) { + super.onDrawerOpened(drawerView); + if (mainActivity.drawerLayout.isDrawerOpen(GravityCompat.END)) { + int current = mainActivity.pager.getCurrentItem(); + + if (current == mainActivity.toOpenComments && mainActivity.toOpenComments != 0) { + current -= 1; + } + String compare = mainActivity.usedArray.get(current); + if (compare.equals("random") + || compare.equals("myrandom") + || compare.equals("randnsfw")) { + if (mainActivity.adapter != null + && mainActivity.adapter.getCurrentFragment() != null + && ((SubmissionsView) mainActivity.adapter.getCurrentFragment()) + .adapter + .dataSet + .subredditRandom + != null) { + String sub = + ((SubmissionsView) mainActivity.adapter.getCurrentFragment()) + .adapter + .dataSet + .subredditRandom; + mainActivity.doSubSidebarNoLoad(sub); + mainActivity.doSubSidebar(sub); + } + } else { + mainActivity.doSubSidebar(mainActivity.usedArray.get(current)); + } + } + } + + @Override + public void onDrawerClosed(View view) { + super.onDrawerClosed(view); + KeyboardUtil.hideKeyboard( + mainActivity, mainActivity.drawerLayout.getWindowToken(), 0); + } + }; + + mainActivity.drawerLayout.addDrawerListener(actionBarDrawerToggle); + + actionBarDrawerToggle.syncState(); + header.findViewById(R.id.back).setBackgroundColor(Palette.getColor("alsdkfjasld")); + accountsArea = header.findViewById(R.id.accountsarea); + if (accountsArea != null) { + accountsArea.setBackgroundColor(Palette.getDarkerColor("alsdkfjasld")); + } + + setDrawerSubList(); + hideDrawerItems(); + } + + public void hideDrawerItems() { + for (DrawerItemsDialog.SettingsDrawerEnum settingDrawerItem : + DrawerItemsDialog.SettingsDrawerEnum.values()) { + View drawerItem = drawerSubList.findViewById(settingDrawerItem.drawerId); + if (drawerItem != null + && drawerItem.getVisibility() == View.VISIBLE + && (SettingValues.selectedDrawerItems & settingDrawerItem.value) == 0) { + drawerItem.setVisibility(View.GONE); + } + } + } + + public void setDrawerSubList() { + + ArrayList copy; + + if (NetworkUtil.isConnected(mainActivity)) { + copy = new ArrayList<>(mainActivity.usedArray); + } else { + copy = UserSubscriptions.getAllUserSubreddits(mainActivity); + } + + copy.removeAll(Arrays.asList("", null)); + + sideArrayAdapter = + new SideArrayAdapter( + mainActivity, copy, UserSubscriptions.getAllSubreddits(mainActivity), drawerSubList); + drawerSubList.setAdapter(sideArrayAdapter); + + if ((SettingValues.subredditSearchMethod != Constants.SUBREDDIT_SEARCH_METHOD_TOOLBAR)) { + drawerSearch = mainActivity.headerMain.findViewById(R.id.sort); + drawerSearch.setVisibility(View.VISIBLE); + + drawerSubList.setFocusable(false); + + mainActivity.headerMain + .findViewById(R.id.close_search_drawer) + .setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View v) { + drawerSearch.setText(""); + } + }); + + drawerSearch.setOnFocusChangeListener( + new View.OnFocusChangeListener() { + @Override + public void onFocusChange(View v, boolean hasFocus) { + if (hasFocus) { + mainActivity.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN); + drawerSubList.smoothScrollToPositionFromTop(1, drawerSearch.getHeight(), 100); + } else { + mainActivity.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE); + } + } + }); + drawerSearch.setOnEditorActionListener( + new TextView.OnEditorActionListener() { + @Override + public boolean onEditorAction(TextView arg0, int arg1, KeyEvent arg2) { + if (arg2 != null) { + return true; + } + + String searchText = drawerSearch.getText().toString().toLowerCase(Locale.ENGLISH); + boolean searchSubFound = mainActivity.usedArray.contains(searchText); + int searchSubIndex = mainActivity.usedArray.indexOf(searchText); + int sideArrayAdapterIndex = mainActivity.usedArray.indexOf(sideArrayAdapter.fitems.get(0)); + + if (arg1 == EditorInfo.IME_ACTION_SEARCH) { + // If it the input text doesn't match a subreddit from the list exactly, openInSubView is true + if (sideArrayAdapter.fitems == null + || sideArrayAdapter.openInSubView + || !searchSubFound) { + Intent inte = + new Intent(mainActivity, SubredditView.class); + inte.putExtra( + SubredditView.EXTRA_SUBREDDIT, + searchText); + mainActivity.startActivityForResult(inte, 2001); + } else { + if (mainActivity.commentPager + && mainActivity.adapter instanceof MainPagerAdapterComment) { + mainActivity.openingComments = null; + mainActivity.toOpenComments = -1; + ((MainPagerAdapterComment) mainActivity.adapter).size = + (mainActivity.usedArray.size() + 1); + mainActivity.adapter.notifyDataSetChanged(); + if (!searchSubFound) { + mainActivity.doPageSelectedComments(sideArrayAdapterIndex); + } else { + mainActivity.doPageSelectedComments(searchSubIndex); + } + } + if (!searchSubFound) { + mainActivity.pager.setCurrentItem(sideArrayAdapterIndex); + } else { + mainActivity.pager.setCurrentItem(searchSubIndex); + } + + mainActivity.drawerLayout.closeDrawers(); + drawerSearch.setText(""); + View view = mainActivity.getCurrentFocus(); + if (view != null) { + KeyboardUtil.hideKeyboard( + mainActivity, view.getWindowToken(), 0); + } + } + } + return false; + } + }); + + final View close = mainActivity.findViewById(R.id.close_search_drawer); + close.setVisibility(View.GONE); + + drawerSearch.addTextChangedListener( + new SimpleTextWatcher() { + @Override + public void afterTextChanged(Editable editable) { + final String result = editable.toString(); + if (result.isEmpty()) { + close.setVisibility(View.GONE); + } else { + close.setVisibility(View.VISIBLE); + } + sideArrayAdapter.getFilter().filter(result); + } + }); + } else { + if (drawerSearch != null) { + drawerSearch.setOnClickListener(null); + drawerSearch.setVisibility(View.GONE); + } + } + } + + private void collapse(final LinearLayout v) { + int finalHeight = v.getHeight(); + + ValueAnimator mAnimator = AnimatorUtil.slideAnimator(finalHeight, 0, v); + + mAnimator.addListener( + new AnimatorListenerAdapter() { + @Override + public void onAnimationEnd(Animator animator) { + v.setVisibility(View.GONE); + } + }); + mAnimator.start(); + } + + private void expand(LinearLayout v) { + // set Visible + v.setVisibility(View.VISIBLE); + + final int widthSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); + final int heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); + v.measure(widthSpec, heightSpec); + + ValueAnimator mAnimator = AnimatorUtil.slideAnimator(0, v.getMeasuredHeight(), v); + mAnimator.start(); + } + + private void showUsernameDialog(boolean isMultireddit) { + final Context contextThemeWrapper = new ContextThemeWrapper(mainActivity, + new ColorPreferences(mainActivity).getFontStyle().getBaseId()); + + // Create TextInputLayout for proper error handling + TextInputLayout inputLayout = new TextInputLayout(contextThemeWrapper); + inputLayout.setErrorIconDrawable(null); + inputLayout.setErrorEnabled(true); + + final EditText input = new EditText(contextThemeWrapper); + input.setHint(mainActivity.getString(R.string.user_enter)); + input.setHintTextColor(mainActivity.getResources().getColor(R.color.md_grey_700)); + input.setSingleLine(true); + input.setInputType(InputType.TYPE_CLASS_TEXT); + + // Match search box styling + int underlineColor = new ColorPreferences(contextThemeWrapper).getColor(mainActivity.selectedSub); + input.getBackground().setColorFilter(underlineColor, android.graphics.PorterDuff.Mode.SRC_ATOP); + + // Add EditText to TextInputLayout + inputLayout.addView(input); + + FrameLayout frameLayout = new FrameLayout(contextThemeWrapper); + int padding = (int) TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, 24, mainActivity.getResources().getDisplayMetrics()); + frameLayout.setPadding(padding, 0, padding, 0); + frameLayout.addView(inputLayout, new FrameLayout.LayoutParams( + FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.WRAP_CONTENT)); + + int positiveButtonText = R.string.user_btn_goto; + + if (isMultireddit) { + positiveButtonText = R.string.user_btn_gotomultis; + } + + MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(contextThemeWrapper) + .setTitle(R.string.user_enter) + .setView(frameLayout) + .setPositiveButton(positiveButtonText, null) + .setNegativeButton(R.string.btn_cancel, null); + + AlertDialog dialog = builder.create(); + dialog.show(); + + // Set accent color for buttons + int accentColor = new ColorPreferences(contextThemeWrapper).getColor(mainActivity.selectedSub); + dialog.getButton(DialogInterface.BUTTON_POSITIVE).setTextColor(accentColor); + dialog.getButton(DialogInterface.BUTTON_NEGATIVE).setTextColor(accentColor); + + // Set up the positive button click listener after dialog is shown + Button positiveButton = dialog.getButton(DialogInterface.BUTTON_POSITIVE); + positiveButton.setEnabled(true); + positiveButton.setOnClickListener(view1 -> { + String username = input.getText().toString().trim(); + if (username.length() >= 3 && username.length() <= 20) { + Intent inte; + if (isMultireddit) { + inte = new Intent(mainActivity, MultiredditOverview.class); + } else { + inte = new Intent(mainActivity, Profile.class); + } + inte.putExtra(Profile.EXTRA_PROFILE, username); + mainActivity.startActivity(inte); + dialog.dismiss(); + } else { + inputLayout.setError("Username must be between 3 and 20 characters"); + } + }); + + // Clear error when text changes + input.addTextChangedListener(new TextWatcher() { + @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {} + @Override public void onTextChanged(CharSequence s, int start, int before, int count) { + inputLayout.setError(null); + } + @Override public void afterTextChanged(Editable s) {} + }); + } +} \ No newline at end of file diff --git a/app/src/main/java/me/edgan/redditslide/Activities/MainActivity.java b/app/src/main/java/me/edgan/redditslide/Activities/MainActivity.java index 805e929eb..76d07ae3b 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/MainActivity.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/MainActivity.java @@ -1,10 +1,6 @@ package me.edgan.redditslide.Activities; -import static me.edgan.redditslide.UserSubscriptions.modOf; -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.ValueAnimator; import android.app.Activity; import android.app.Dialog; import android.content.Context; @@ -25,7 +21,6 @@ import android.os.Build; import android.os.Bundle; import android.os.Handler; -import android.os.Parcelable; import android.text.Editable; import android.text.InputType; import android.text.Spannable; @@ -43,26 +38,22 @@ import android.view.ViewStub; import android.view.ViewTreeObserver; import android.view.Window; -import android.view.WindowManager; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.LinearInterpolator; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodManager; import android.widget.AutoCompleteTextView; -import android.widget.Button; import android.widget.CompoundButton; import android.widget.EditText; import android.widget.FrameLayout; import android.widget.HorizontalScrollView; import android.widget.ImageView; -import android.widget.LinearLayout; import android.widget.ListView; import android.widget.TextView; import android.widget.Toast; import androidx.annotation.DrawableRes; import androidx.annotation.NonNull; -import androidx.appcompat.app.ActionBarDrawerToggle; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.view.ContextThemeWrapper; import androidx.appcompat.widget.AppCompatCheckBox; @@ -77,10 +68,7 @@ import androidx.core.view.GravityCompat; import androidx.customview.widget.ViewDragHelper; import androidx.drawerlayout.widget.DrawerLayout; -import androidx.fragment.app.Fragment; -import androidx.fragment.app.FragmentManager; import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.viewpager.widget.ViewPager; import com.afollestad.materialdialogs.DialogAction; import com.afollestad.materialdialogs.MaterialDialog; @@ -88,7 +76,6 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.snackbar.Snackbar; import com.google.android.material.tabs.TabLayout; -import com.google.android.material.textfield.TextInputLayout; import com.lusfold.androidkeyvaluestore.KVStore; import com.lusfold.androidkeyvaluestore.core.KVManger; @@ -101,8 +88,6 @@ import me.edgan.redditslide.CommentCacheAsync; import me.edgan.redditslide.Constants; import me.edgan.redditslide.ForceTouch.util.DensityUtils; -import me.edgan.redditslide.Fragments.CommentPage; -import me.edgan.redditslide.Fragments.DrawerItemsDialog; import me.edgan.redditslide.Fragments.SubmissionsView; import me.edgan.redditslide.ImageFlairs; import me.edgan.redditslide.Notifications.CheckForMail; @@ -120,14 +105,11 @@ import me.edgan.redditslide.Views.ToggleSwipeViewPager; import me.edgan.redditslide.Visuals.ColorPreferences; import me.edgan.redditslide.Visuals.Palette; -import me.edgan.redditslide.ui.settings.ManageOfflineContent; import me.edgan.redditslide.ui.settings.SettingsActivity; import me.edgan.redditslide.ui.settings.SettingsGeneralFragment; import me.edgan.redditslide.ui.settings.SettingsSubAdapter; import me.edgan.redditslide.ui.settings.SettingsThemeFragment; -import me.edgan.redditslide.util.AnimatorUtil; import me.edgan.redditslide.util.DrawableUtil; -import me.edgan.redditslide.util.EditTextValidator; import me.edgan.redditslide.util.ImageUtil; import me.edgan.redditslide.util.KeyboardUtil; import me.edgan.redditslide.util.LayoutUtils; @@ -162,14 +144,12 @@ import java.lang.reflect.Field; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; -import java.util.Set; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -232,13 +212,15 @@ public class MainActivity extends BaseActivity AsyncTask caching; boolean currentlySubbed; int back; - private AsyncGetSubredditTask mAsyncGetSubreddit = null; + AsyncGetSubredditTask mAsyncGetSubreddit = null; private int headerHeight; // height of the header public int reloadItemNumber = -2; private static final int NOTIFICATION_PERMISSION_REQUEST_CODE = 1001; private View rootView; + DrawerController drawerController; + @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == SETTINGS_RESULT) { @@ -299,7 +281,7 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) { } } else if (requestCode == RESET_ADAPTER_RESULT) { resetAdapter(); - setDrawerSubList(); + drawerController.setDrawerSubList(); } else if (requestCode == TUTORIAL_RESULT) { UserSubscriptions.doMainActivitySubs(this); } else if (requestCode == INBOX_RESULT) { @@ -978,6 +960,7 @@ public void onSingleClick( applyColorTheme(); setContentView(R.layout.activity_overview); + drawerController = new DrawerController(this); rootView = findViewById(android.R.id.content); @@ -1203,7 +1186,7 @@ public void onResume() { // Need to change the subreddit search method if (SettingsGeneralFragment.searchChanged) { - setDrawerSubList(); + drawerController.setDrawerSubList(); if (SettingValues.subredditSearchMethod == Constants.SUBREDDIT_SEARCH_METHOD_DRAWER) { @@ -1217,7 +1200,7 @@ public void onResume() { == Constants.SUBREDDIT_SEARCH_METHOD_BOTH) { findViewById(R.id.drawer_divider).setVisibility(View.GONE); setupSubredditSearchToolbar(); - setDrawerSubList(); + drawerController.setDrawerSubList(); } SettingsGeneralFragment.searchChanged = false; } @@ -1279,956 +1262,7 @@ private static void setDrawerEdge( public HashMap accounts = new HashMap<>(); - public void doDrawer() { - drawerSubList = (ListView) findViewById(R.id.drawerlistview); - drawerSubList.setDividerHeight(0); - drawerSubList.setDescendantFocusability(ListView.FOCUS_BEFORE_DESCENDANTS); - final LayoutInflater inflater = getLayoutInflater(); - final View header; - - if (Authentication.isLoggedIn && Authentication.didOnline) { - - header = inflater.inflate(R.layout.drawer_loggedin, drawerSubList, false); - headerMain = header; - hea = header.findViewById(R.id.back); - - drawerSubList.addHeaderView(header, null, false); - ((TextView) header.findViewById(R.id.name)).setText(Authentication.name); - header.findViewById(R.id.multi) - .setOnClickListener( - new OnSingleClickListener() { - @Override - public void onSingleClick(View view) { - if (runAfterLoad == null) { - Intent inte = - new Intent( - MainActivity.this, - MultiredditOverview.class); - MainActivity.this.startActivity(inte); - } - } - }); - header.findViewById(R.id.multi).setOnLongClickListener(new View.OnLongClickListener() { - @Override - public boolean onLongClick(View view) { - showUsernameDialog(true); // true for multireddit view - return true; - } - }); - - header.findViewById(R.id.discover) - .setOnClickListener( - new OnSingleClickListener() { - @Override - public void onSingleClick(View view) { - Intent inte = new Intent(MainActivity.this, Discover.class); - MainActivity.this.startActivity(inte); - } - }); - - header.findViewById(R.id.prof_click) - .setOnClickListener( - new OnSingleClickListener() { - @Override - public void onSingleClick(View view) { - Intent inte = new Intent(MainActivity.this, Profile.class); - inte.putExtra(Profile.EXTRA_PROFILE, Authentication.name); - MainActivity.this.startActivity(inte); - } - }); - header.findViewById(R.id.saved) - .setOnClickListener( - new OnSingleClickListener() { - @Override - public void onSingleClick(View view) { - Intent inte = new Intent(MainActivity.this, Profile.class); - inte.putExtra(Profile.EXTRA_PROFILE, Authentication.name); - inte.putExtra(Profile.EXTRA_SAVED, true); - MainActivity.this.startActivity(inte); - } - }); - header.findViewById(R.id.later) - .setOnClickListener( - new OnSingleClickListener() { - @Override - public void onSingleClick(View view) { - Intent inte = - new Intent(MainActivity.this, PostReadLater.class); - MainActivity.this.startActivity(inte); - } - }); - header.findViewById(R.id.history) - .setOnClickListener( - new OnSingleClickListener() { - @Override - public void onSingleClick(View view) { - Intent inte = new Intent(MainActivity.this, Profile.class); - inte.putExtra(Profile.EXTRA_PROFILE, Authentication.name); - inte.putExtra(Profile.EXTRA_HISTORY, true); - MainActivity.this.startActivity(inte); - } - }); - header.findViewById(R.id.commented) - .setOnClickListener( - new OnSingleClickListener() { - @Override - public void onSingleClick(View view) { - Intent inte = new Intent(MainActivity.this, Profile.class); - inte.putExtra(Profile.EXTRA_PROFILE, Authentication.name); - inte.putExtra(Profile.EXTRA_COMMENT, true); - MainActivity.this.startActivity(inte); - } - }); - header.findViewById(R.id.submitted) - .setOnClickListener( - new OnSingleClickListener() { - @Override - public void onSingleClick(View view) { - Intent inte = new Intent(MainActivity.this, Profile.class); - inte.putExtra(Profile.EXTRA_PROFILE, Authentication.name); - inte.putExtra(Profile.EXTRA_SUBMIT, true); - MainActivity.this.startActivity(inte); - } - }); - header.findViewById(R.id.upvoted) - .setOnClickListener( - new OnSingleClickListener() { - @Override - public void onSingleClick(View view) { - Intent inte = new Intent(MainActivity.this, Profile.class); - inte.putExtra(Profile.EXTRA_PROFILE, Authentication.name); - inte.putExtra(Profile.EXTRA_UPVOTE, true); - MainActivity.this.startActivity(inte); - } - }); - - /** - * If the user is a known mod, show the "Moderation" drawer item quickly to stop the UI - * from jumping - */ - if (modOf != null && !modOf.isEmpty() && Authentication.mod) { - header.findViewById(R.id.mod).setVisibility(View.VISIBLE); - } - - // update notification badge - final LinearLayout profStuff = header.findViewById(R.id.accountsarea); - profStuff.setVisibility(View.GONE); - findViewById(R.id.back) - .setOnClickListener( - new View.OnClickListener() { - @Override - public void onClick(View v) { - if (profStuff.getVisibility() == View.GONE) { - expand(profStuff); - header.setContentDescription( - getResources().getString(R.string.btn_collapse)); - AnimatorUtil.flipAnimator( - false, header.findViewById(R.id.headerflip)) - .start(); - } else { - collapse(profStuff); - header.setContentDescription( - getResources().getString(R.string.btn_expand)); - AnimatorUtil.flipAnimator( - true, header.findViewById(R.id.headerflip)) - .start(); - } - } - }); - - for (String s : - Authentication.authentication.getStringSet("accounts", new HashSet())) { - if (s.contains(":")) { - accounts.put(s.split(":")[0], s.split(":")[1]); - } else { - accounts.put(s, ""); - } - } - final ArrayList keys = new ArrayList<>(accounts.keySet()); - - final LinearLayout accountList = header.findViewById(R.id.accountsarea); - for (final String accName : keys) { - LogUtil.v(accName); - final View t = - getLayoutInflater() - .inflate(R.layout.account_textview_white, accountList, false); - ((TextView) t.findViewById(R.id.name)).setText(accName); - LogUtil.v("Adding click to " + ((TextView) t.findViewById(R.id.name)).getText()); - t.findViewById(R.id.remove) - .setOnClickListener( - new View.OnClickListener() { - @Override - public void onClick(View v) { - - final Context contextThemeWrapper = new ContextThemeWrapper(MainActivity.this, - new ColorPreferences(MainActivity.this).getFontStyle().getBaseId()); - - new MaterialAlertDialogBuilder(contextThemeWrapper) - .setTitle(R.string.profile_remove) - .setMessage(R.string.profile_remove_account) - .setNegativeButton( - R.string.btn_delete, - (dialog2, which2) -> { - Set accounts2 = - Authentication.authentication - .getStringSet( - "accounts", - new HashSet<>()); - Set done = new HashSet<>(); - for (String s : accounts2) { - if (!s.contains(accName)) { - done.add(s); - } - } - Authentication.authentication - .edit() - .putStringSet("accounts", done) - .commit(); - dialog2.dismiss(); - accountList.removeView(t); - if (accName.equalsIgnoreCase( - Authentication.name)) { - - boolean d = false; - for (String s : keys) { - - if (!s.equalsIgnoreCase( - accName)) { - d = true; - LogUtil.v( - "Switching to " - + s); - for (Map.Entry< - String, - String> - e : - accounts - .entrySet()) { - LogUtil.v( - e.getKey() - + ":" - + e - .getValue()); - } - if (accounts.containsKey(s) - && !accounts.get(s) - .isEmpty()) { - Authentication - .authentication - .edit() - .putString( - "lasttoken", - accounts - .get( - s)) - .remove( - "backedCreds") - .commit(); - } else { - ArrayList - tokens = - new ArrayList<>( - Authentication - .authentication - .getStringSet( - "tokens", - new HashSet<>())); - int index = - keys.indexOf(s); - if (keys.indexOf(s) - > tokens - .size()) { - index -= 1; - } - Authentication - .authentication - .edit() - .putString( - "lasttoken", - tokens - .get( - index)) - .remove( - "backedCreds") - .commit(); - } - Authentication.name = s; - UserSubscriptions - .switchAccounts(); - Reddit.forceRestart( - MainActivity.this, - true); - break; - } - } - if (!d) { - Authentication.name = - "LOGGEDOUT"; - Authentication.isLoggedIn = - false; - Authentication.authentication - .edit() - .remove("lasttoken") - .remove("backedCreds") - .commit(); - UserSubscriptions - .switchAccounts(); - Reddit.forceRestart( - MainActivity.this, - true); - } - } else { - accounts.remove(accName); - keys.remove(accName); - } - }) - .setPositiveButton(R.string.btn_cancel, null) - .show(); - } - }); - t.setOnClickListener( - new View.OnClickListener() { - @Override - public void onClick(View v) { - String accName = - ((TextView) t.findViewById(R.id.name)).getText().toString(); - LogUtil.v("Found name is " + accName); - if (!accName.equalsIgnoreCase(Authentication.name)) { - LogUtil.v("Switching to " + accName); - if (!accounts.get(accName).isEmpty()) { - LogUtil.v("Using token " + accounts.get(accName)); - Authentication.authentication - .edit() - .putString("lasttoken", accounts.get(accName)) - .remove("backedCreds") - .apply(); - } else { - ArrayList tokens = - new ArrayList<>( - Authentication.authentication.getStringSet( - "tokens", new HashSet())); - Authentication.authentication - .edit() - .putString( - "lasttoken", - tokens.get(keys.indexOf(accName))) - .remove("backedCreds") - .apply(); - } - Authentication.name = accName; - - UserSubscriptions.switchAccounts(); - - Reddit.forceRestart(MainActivity.this, true); - } - } - }); - accountList.addView(t); - } - - header.findViewById(R.id.godown) - .setOnClickListener( - new View.OnClickListener() { - @Override - public void onClick(View view) { - LinearLayout body = header.findViewById(R.id.expand_profile); - if (body.getVisibility() == View.GONE) { - expand(body); - AnimatorUtil.flipAnimator(false, view).start(); - view.findViewById(R.id.godown) - .setContentDescription( - getResources() - .getString(R.string.btn_collapse)); - } else { - collapse(body); - AnimatorUtil.flipAnimator(true, view).start(); - view.findViewById(R.id.godown) - .setContentDescription( - getResources() - .getString(R.string.btn_expand)); - } - } - }); - - header.findViewById(R.id.guest_mode) - .setOnClickListener( - new OnSingleClickListener() { - @Override - public void onSingleClick(View v) { - Authentication.name = "LOGGEDOUT"; - Authentication.isLoggedIn = false; - Authentication.authentication - .edit() - .remove("lasttoken") - .remove("backedCreds") - .apply(); - UserSubscriptions.switchAccounts(); - Reddit.forceRestart(MainActivity.this, true); - } - }); - - header.findViewById(R.id.add) - .setOnClickListener( - new OnSingleClickListener() { - @Override - public void onSingleClick(View view) { - Intent inte = new Intent(MainActivity.this, Login.class); - MainActivity.this.startActivity(inte); - } - }); - header.findViewById(R.id.offline) - .setOnClickListener( - new OnSingleClickListener() { - @Override - public void onSingleClick(View view) { - Reddit.appRestart - .edit() - .putBoolean("forceoffline", true) - .commit(); - Reddit.forceRestart(MainActivity.this, false); - } - }); - header.findViewById(R.id.inbox) - .setOnClickListener( - new OnSingleClickListener() { - @Override - public void onSingleClick(View view) { - Intent inte = new Intent(MainActivity.this, Inbox.class); - MainActivity.this.startActivityForResult(inte, INBOX_RESULT); - } - }); - - headerMain = header; - - if (runAfterLoad == null) { - new AsyncNotificationBadge(this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } - - } else if (Authentication.didOnline) { - header = inflater.inflate(R.layout.drawer_loggedout, drawerSubList, false); - drawerSubList.addHeaderView(header, null, false); - headerMain = header; - hea = header.findViewById(R.id.back); - - final LinearLayout profStuff = header.findViewById(R.id.accountsarea); - profStuff.setVisibility(View.GONE); - findViewById(R.id.back) - .setOnClickListener( - new View.OnClickListener() { - @Override - public void onClick(View v) { - if (profStuff.getVisibility() == View.GONE) { - expand(profStuff); - AnimatorUtil.flipAnimator( - false, header.findViewById(R.id.headerflip)) - .start(); - header.findViewById(R.id.headerflip) - .setContentDescription( - getResources() - .getString(R.string.btn_collapse)); - } else { - collapse(profStuff); - AnimatorUtil.flipAnimator( - true, header.findViewById(R.id.headerflip)) - .start(); - header.findViewById(R.id.headerflip) - .setContentDescription( - getResources() - .getString(R.string.btn_expand)); - } - } - }); - final HashMap accounts = new HashMap<>(); - - for (String s : - Authentication.authentication.getStringSet("accounts", new HashSet())) { - if (s.contains(":")) { - accounts.put(s.split(":")[0], s.split(":")[1]); - } else { - accounts.put(s, ""); - } - } - final ArrayList keys = new ArrayList<>(accounts.keySet()); - - final LinearLayout accountList = header.findViewById(R.id.accountsarea); - for (final String accName : keys) { - LogUtil.v(accName); - final View t = - getLayoutInflater() - .inflate(R.layout.account_textview_white, accountList, false); - ((TextView) t.findViewById(R.id.name)).setText(accName); - t.findViewById(R.id.remove) - .setOnClickListener( - new View.OnClickListener() { - @Override - public void onClick(View v) { - - new AlertDialog.Builder(MainActivity.this) - .setTitle(R.string.profile_remove) - .setMessage(R.string.profile_remove_account) - .setNegativeButton( - R.string.btn_delete, - (dialog2, which2) -> { - Set accounts2 = - Authentication.authentication - .getStringSet( - "accounts", - new HashSet<>()); - Set done = new HashSet<>(); - for (String s : accounts2) { - if (!s.contains(accName)) { - done.add(s); - } - } - Authentication.authentication - .edit() - .putStringSet("accounts", done) - .commit(); - dialog2.dismiss(); - accountList.removeView(t); - - if (accName.equalsIgnoreCase( - Authentication.name)) { - boolean d = false; - for (String s : keys) { - if (!s.equalsIgnoreCase( - accName)) { - d = true; - LogUtil.v( - "Switching to " - + s); - if (!accounts.get(s) - .isEmpty()) { - Authentication - .authentication - .edit() - .putString( - "lasttoken", - accounts - .get( - s)) - .remove( - "backedCreds") - .commit(); - - } else { - ArrayList - tokens = - new ArrayList<>( - Authentication - .authentication - .getStringSet( - "tokens", - new HashSet<>())); - Authentication - .authentication - .edit() - .putString( - "lasttoken", - tokens - .get( - keys - .indexOf( - s))) - .remove( - "backedCreds") - .commit(); - } - Authentication.name = s; - UserSubscriptions - .switchAccounts(); - Reddit.forceRestart( - MainActivity.this, - true); - } - } - if (!d) { - Authentication.name = - "LOGGEDOUT"; - Authentication.isLoggedIn = - false; - Authentication.authentication - .edit() - .remove("lasttoken") - .remove("backedCreds") - .commit(); - UserSubscriptions - .switchAccounts(); - Reddit.forceRestart( - MainActivity.this, - true); - } - } else { - accounts.remove(accName); - keys.remove(accName); - } - }) - .setPositiveButton(R.string.btn_cancel, null) - .show(); - } - }); - t.setOnClickListener( - new OnSingleClickListener() { - @Override - public void onSingleClick(View v) { - if (!accName.equalsIgnoreCase(Authentication.name)) { - if (!accounts.get(accName).isEmpty()) { - Authentication.authentication - .edit() - .putString("lasttoken", accounts.get(accName)) - .remove("backedCreds") - .commit(); - } else { - ArrayList tokens = - new ArrayList<>( - Authentication.authentication.getStringSet( - "tokens", new HashSet())); - Authentication.authentication - .edit() - .putString( - "lasttoken", - tokens.get(keys.indexOf(accName))) - .remove("backedCreds") - .commit(); - } - - // Removing this will break Guest mode - Authentication.isLoggedIn = true; - - Authentication.name = accName; - - UserSubscriptions.switchAccounts(); - - Reddit.forceRestart(MainActivity.this, true); - } - } - }); - accountList.addView(t); - } - - header.findViewById(R.id.add) - .setOnClickListener( - new OnSingleClickListener() { - @Override - public void onSingleClick(View view) { - Intent inte = new Intent(MainActivity.this, Login.class); - MainActivity.this.startActivity(inte); - } - }); - header.findViewById(R.id.offline) - .setOnClickListener( - new OnSingleClickListener() { - @Override - public void onSingleClick(View view) { - Reddit.appRestart - .edit() - .putBoolean("forceoffline", true) - .commit(); - Reddit.forceRestart(MainActivity.this, false); - } - }); - headerMain = header; - - header.findViewById(R.id.multi) - .setOnClickListener( - new View.OnClickListener() { - @Override - public void onClick(View view) { - new MaterialDialog.Builder(MainActivity.this) - .inputRange(3, 20) - .alwaysCallInputCallback() - .input( - getString(R.string.user_enter), - null, - new MaterialDialog.InputCallback() { - @Override - public void onInput( - @NonNull MaterialDialog dialog, - CharSequence input) { - final EditText editText = - dialog.getInputEditText(); - EditTextValidator.validateUsername( - editText); - if (input.length() >= 3 - && input.length() <= 20) { - dialog.getActionButton( - DialogAction - .POSITIVE) - .setEnabled(true); - } - } - }) - .positiveText(R.string.user_btn_gotomultis) - .onPositive( - new MaterialDialog.SingleButtonCallback() { - @Override - public void onClick( - @NonNull MaterialDialog dialog, - @NonNull DialogAction which) { - if (runAfterLoad == null) { - Intent inte = - new Intent( - MainActivity.this, - MultiredditOverview - .class); - inte.putExtra( - Profile.EXTRA_PROFILE, - dialog.getInputEditText() - .getText() - .toString()); - MainActivity.this.startActivity( - inte); - } - } - }) - .negativeText(R.string.btn_cancel) - .show(); - } - }); - - } else { - header = inflater.inflate(R.layout.drawer_offline, drawerSubList, false); - headerMain = header; - drawerSubList.addHeaderView(header, null, false); - hea = header.findViewById(R.id.back); - - header.findViewById(R.id.online) - .setOnClickListener( - new OnSingleClickListener() { - @Override - public void onSingleClick(View view) { - Reddit.appRestart.edit().remove("forceoffline").commit(); - Reddit.forceRestart(MainActivity.this, false); - } - }); - } - - final LinearLayout expandSettings = header.findViewById(R.id.expand_settings); - header.findViewById(R.id.godown_settings) - .setOnClickListener( - new View.OnClickListener() { - @Override - public void onClick(View v) { - if (expandSettings.getVisibility() == View.GONE) { - expand(expandSettings); - header.findViewById(R.id.godown_settings) - .setContentDescription( - getResources() - .getString(R.string.btn_collapse)); - AnimatorUtil.flipAnimator(false, v).start(); - } else { - collapse(expandSettings); - header.findViewById(R.id.godown_settings) - .setContentDescription( - getResources().getString(R.string.btn_expand)); - AnimatorUtil.flipAnimator(true, v).start(); - } - } - }); - - { // Set up quick setting toggles - final SwitchCompat toggleNightMode = - expandSettings.findViewById(R.id.toggle_night_mode); - toggleNightMode.setVisibility(View.VISIBLE); - toggleNightMode.setChecked(inNightMode); - toggleNightMode.setOnCheckedChangeListener( - new CompoundButton.OnCheckedChangeListener() { - @Override - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - SettingValues.forcedNightModeState = - isChecked - ? SettingValues.ForcedState.FORCED_ON - : SettingValues.ForcedState.FORCED_OFF; - restartTheme(); - } - }); - - final SwitchCompat toggleImmersiveMode = - expandSettings.findViewById(R.id.toggle_immersive_mode); - toggleImmersiveMode.setChecked(SettingValues.immersiveMode); - toggleImmersiveMode.setOnCheckedChangeListener( - new CompoundButton.OnCheckedChangeListener() { - @Override - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - SettingValues.immersiveMode = isChecked; - SettingValues.prefs - .edit() - .putBoolean(SettingValues.PREF_IMMERSIVE_MODE, isChecked) - .apply(); - if (isChecked) { - hideDecor(); - } else { - showDecor(); - } - } - }); - - final SwitchCompat toggleNSFW = expandSettings.findViewById(R.id.toggle_nsfw); - toggleNSFW.setChecked(SettingValues.showNSFWContent); - toggleNSFW.setOnCheckedChangeListener( - new CompoundButton.OnCheckedChangeListener() { - @Override - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - SettingValues.showNSFWContent = isChecked; - SettingValues.prefs - .edit() - .putBoolean(SettingValues.PREF_SHOW_NSFW_CONTENT, isChecked) - .apply(); - reloadSubs(); - } - }); - - final SwitchCompat toggleRightThumbnails = - expandSettings.findViewById(R.id.toggle_right_thumbnails); - toggleRightThumbnails.setChecked(SettingValues.switchThumb); - toggleRightThumbnails.setOnCheckedChangeListener( - new CompoundButton.OnCheckedChangeListener() { - @Override - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - SettingValues.switchThumb = isChecked; - SettingValues.prefs - .edit() - .putBoolean(SettingValues.PREF_SWITCH_THUMB, isChecked) - .apply(); - reloadSubs(); - } - }); - - final SwitchCompat toggleReaderMode = - expandSettings.findViewById(R.id.toggle_reader_mode); - toggleReaderMode.setChecked(SettingValues.readerMode); - toggleReaderMode.setOnCheckedChangeListener( - new CompoundButton.OnCheckedChangeListener() { - @Override - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - SettingValues.readerMode = isChecked; - SettingValues.prefs - .edit() - .putBoolean(SettingValues.PREF_READER_MODE, isChecked) - .apply(); - } - }); - } - - header.findViewById(R.id.manage) - .setOnClickListener( - new OnSingleClickListener() { - @Override - public void onSingleClick(View view) { - Intent i = - new Intent(MainActivity.this, ManageOfflineContent.class); - startActivity(i); - } - }); - if (Authentication.didOnline) { - View support = header.findViewById(R.id.support); - - support.setVisibility(View.GONE); - header.findViewById(R.id.prof) - .setOnClickListener(new OnSingleClickListener() { - @Override - public void onSingleClick(View view) { - showUsernameDialog(false); // false for profile view - } - }); - - } - - header.findViewById(R.id.settings) - .setOnClickListener( - new OnSingleClickListener() { - @Override - public void onSingleClick(View v) { - Intent i = new Intent(MainActivity.this, SettingsActivity.class); - startActivity(i); - // Cancel sub loading because exiting the settings will reload it - // anyway - if (mAsyncGetSubreddit != null) mAsyncGetSubreddit.cancel(true); - drawerLayout.closeDrawers(); - } - }); - - final Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); - - final ActionBarDrawerToggle actionBarDrawerToggle = - new ActionBarDrawerToggle( - MainActivity.this, - drawerLayout, - toolbar, - R.string.btn_open, - R.string.btn_close) { - @Override - public void onDrawerSlide(View drawerView, float slideOffset) { - super.onDrawerSlide(drawerView, 0); // this disables the animation - } - - @Override - public void onDrawerOpened(View drawerView) { - super.onDrawerOpened(drawerView); - if (drawerLayout.isDrawerOpen(GravityCompat.END)) { - int current = pager.getCurrentItem(); - - if (current == toOpenComments && toOpenComments != 0) { - current -= 1; - } - String compare = usedArray.get(current); - if (compare.equals("random") - || compare.equals("myrandom") - || compare.equals("randnsfw")) { - if (adapter != null - && adapter.getCurrentFragment() != null - && ((SubmissionsView) adapter.getCurrentFragment()) - .adapter - .dataSet - .subredditRandom - != null) { - String sub = - ((SubmissionsView) adapter.getCurrentFragment()) - .adapter - .dataSet - .subredditRandom; - doSubSidebarNoLoad(sub); - doSubSidebar(sub); - } - } else { - doSubSidebar(usedArray.get(current)); - } - } - } - - @Override - public void onDrawerClosed(View view) { - super.onDrawerClosed(view); - KeyboardUtil.hideKeyboard( - MainActivity.this, drawerLayout.getWindowToken(), 0); - } - }; - - drawerLayout.addDrawerListener(actionBarDrawerToggle); - - actionBarDrawerToggle.syncState(); - header.findViewById(R.id.back).setBackgroundColor(Palette.getColor("alsdkfjasld")); - accountsArea = header.findViewById(R.id.accountsarea); - if (accountsArea != null) { - accountsArea.setBackgroundColor(Palette.getDarkerColor("alsdkfjasld")); - } - - setDrawerSubList(); - hideDrawerItems(); - } - - public void hideDrawerItems() { - for (DrawerItemsDialog.SettingsDrawerEnum settingDrawerItem : - DrawerItemsDialog.SettingsDrawerEnum.values()) { - View drawerItem = drawerSubList.findViewById(settingDrawerItem.drawerId); - if (drawerItem != null - && drawerItem.getVisibility() == View.VISIBLE - && (SettingValues.selectedDrawerItems & settingDrawerItem.value) == 0) { - drawerItem.setVisibility(View.GONE); - } - } - } public void doForcePrefs() { HashSet domains = new HashSet<>(); @@ -4141,139 +3175,6 @@ public void setDataSet(List data) { } } - public void setDrawerSubList() { - - ArrayList copy; - - if (NetworkUtil.isConnected(this)) { - copy = new ArrayList<>(usedArray); - } else { - copy = UserSubscriptions.getAllUserSubreddits(this); - } - - copy.removeAll(Arrays.asList("", null)); - - sideArrayAdapter = - new SideArrayAdapter( - this, copy, UserSubscriptions.getAllSubreddits(this), drawerSubList); - drawerSubList.setAdapter(sideArrayAdapter); - - if ((SettingValues.subredditSearchMethod != Constants.SUBREDDIT_SEARCH_METHOD_TOOLBAR)) { - drawerSearch = headerMain.findViewById(R.id.sort); - drawerSearch.setVisibility(View.VISIBLE); - - drawerSubList.setFocusable(false); - - headerMain - .findViewById(R.id.close_search_drawer) - .setOnClickListener( - new View.OnClickListener() { - @Override - public void onClick(View v) { - drawerSearch.setText(""); - } - }); - - drawerSearch.setOnFocusChangeListener( - new View.OnFocusChangeListener() { - @Override - public void onFocusChange(View v, boolean hasFocus) { - if (hasFocus) { - getWindow() - .setSoftInputMode( - WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN); - drawerSubList.smoothScrollToPositionFromTop( - 1, drawerSearch.getHeight(), 100); - } else { - getWindow() - .setSoftInputMode( - WindowManager.LayoutParams - .SOFT_INPUT_ADJUST_RESIZE); - } - } - }); - drawerSearch.setOnEditorActionListener( - new TextView.OnEditorActionListener() { - @Override - public boolean onEditorAction(TextView arg0, int arg1, KeyEvent arg2) { - if (arg2 != null) { - return true; // consume but do nothing - } - - String searchText = drawerSearch.getText().toString().toLowerCase(Locale.ENGLISH); - boolean searchSubFound = usedArray.contains(searchText); - int searchSubIndex = usedArray.indexOf(searchText); - int sideArrayAdapterIndex = usedArray.indexOf(sideArrayAdapter.fitems.get(0)); - - if (arg1 == EditorInfo.IME_ACTION_SEARCH) { - // If it the input text doesn't match a subreddit from the list - // exactly, openInSubView is true - if (sideArrayAdapter.fitems == null - || sideArrayAdapter.openInSubView - || !searchSubFound) { - Intent inte = - new Intent(MainActivity.this, SubredditView.class); - inte.putExtra( - SubredditView.EXTRA_SUBREDDIT, - searchText); - MainActivity.this.startActivityForResult(inte, 2001); - } else { - if (commentPager - && adapter instanceof MainPagerAdapterComment) { - openingComments = null; - toOpenComments = -1; - ((MainPagerAdapterComment) adapter).size = - (usedArray.size() + 1); - adapter.notifyDataSetChanged(); - if (!searchSubFound) { - doPageSelectedComments(sideArrayAdapterIndex); - } else { - doPageSelectedComments(searchSubIndex); - } - } - if (!searchSubFound) { - pager.setCurrentItem(sideArrayAdapterIndex); - } else { - pager.setCurrentItem(searchSubIndex); - } - - drawerLayout.closeDrawers(); - drawerSearch.setText(""); - View view = MainActivity.this.getCurrentFocus(); - if (view != null) { - KeyboardUtil.hideKeyboard( - MainActivity.this, view.getWindowToken(), 0); - } - } - } - return false; - } - }); - - final View close = findViewById(R.id.close_search_drawer); - close.setVisibility(View.GONE); - - drawerSearch.addTextChangedListener( - new SimpleTextWatcher() { - @Override - public void afterTextChanged(Editable editable) { - final String result = editable.toString(); - if (result.isEmpty()) { - close.setVisibility(View.GONE); - } else { - close.setVisibility(View.VISIBLE); - } - sideArrayAdapter.getFilter().filter(result); - } - }); - } else { - if (drawerSearch != null) { - drawerSearch.setOnClickListener( - null); // remove the touch listener on the drawer search field - drawerSearch.setVisibility(View.GONE); - } - } - } public void setToolbarClick() { if (mTabLayout != null) { @@ -4366,7 +3267,7 @@ public void onClick( setDataSet(subs); - doDrawer(); + drawerController.doDrawer(); try { setDataSet(subs); } catch (Exception ignored) { @@ -4376,7 +3277,7 @@ public void onClick( loader = null; } else { setDataSet(subs); - doDrawer(); + drawerController.doDrawer(); } } @@ -4484,20 +3385,6 @@ private void changeSubscription(Subreddit subreddit, boolean isChecked) { } } - private void collapse(final LinearLayout v) { - int finalHeight = v.getHeight(); - - ValueAnimator mAnimator = AnimatorUtil.slideAnimator(finalHeight, 0, v); - - mAnimator.addListener( - new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animator) { - v.setVisibility(View.GONE); - } - }); - mAnimator.start(); - } private void dismissProgressDialog() { if (d != null && d.isShowing()) { @@ -4505,17 +3392,6 @@ private void dismissProgressDialog() { } } - private void expand(LinearLayout v) { - // set Visible - v.setVisibility(View.VISIBLE); - - final int widthSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); - final int heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); - v.measure(widthSpec, heightSpec); - - ValueAnimator mAnimator = AnimatorUtil.slideAnimator(0, v.getMeasuredHeight(), v); - mAnimator.start(); - } private void setViews( String rawHTML, @@ -4823,251 +3699,5 @@ public void afterTextChanged( } } - public class MainPagerAdapterComment extends MainPagerAdapter { - public int size = usedArray.size(); - public Fragment storedFragment; - private CommentPage mCurrentComments; - - public MainPagerAdapterComment(MainActivity mainActivity, FragmentManager fm) { - super(mainActivity, fm); - pager.clearOnPageChangeListeners(); - pager.addOnPageChangeListener( - new ViewPager.SimpleOnPageChangeListener() { - @Override - public void onPageScrolled( - int position, float positionOffset, int positionOffsetPixels) { - if (positionOffset == 0) { - if (position != toOpenComments) { - pager.setSwipeLeftOnly(true); - header.setBackgroundColor( - Palette.getColor(usedArray.get(position))); - doPageSelectedComments(position); - if (position == toOpenComments - 1 - && adapter != null - && adapter.getCurrentFragment() != null) { - SubmissionsView page = - (SubmissionsView) adapter.getCurrentFragment(); - if (page != null && page.adapter != null) { - page.adapter.refreshView(); - } - } - } else { - if (mAsyncGetSubreddit != null) { - mAsyncGetSubreddit.cancel(true); - } - - if (header.getTranslationY() == 0) { - header.animate() - .translationY(-header.getHeight() * 1.5f) - .setInterpolator(new LinearInterpolator()) - .setDuration(180); - } - pager.setSwipeLeftOnly(true); - themeSystemBars( - openingComments - .getSubredditName() - .toLowerCase(Locale.ENGLISH)); - setRecentBar( - openingComments - .getSubredditName() - .toLowerCase(Locale.ENGLISH)); - } - } - } - @Override - public void onPageSelected(final int position) { - if (position == toOpenComments - 1 - && adapter != null - && adapter.getCurrentFragment() != null) { - SubmissionsView page = - (SubmissionsView) adapter.getCurrentFragment(); - if (page != null && page.adapter != null) { - page.adapter.refreshView(); - SubredditPosts p = page.adapter.dataSet; - if (p.offline && !isRestart) { - p.doMainActivityOffline(MainActivity.this, p.displayer); - } - } - } else { - SubmissionsView page = - (SubmissionsView) adapter.getCurrentFragment(); - if (page != null && page.adapter != null) { - SubredditPosts p = page.adapter.dataSet; - if (p.offline && !isRestart) { - p.doMainActivityOffline(MainActivity.this, p.displayer); - } - } - } - } - }); - notifyDataSetChanged(); - } - - @Override - public int getCount() { - if (usedArray == null) { - return 1; - } else { - return size; - } - } - - @NonNull - @Override - public Fragment getItem(int i) { - if (openingComments == null || i != toOpenComments) { - SubmissionsView f = new SubmissionsView(); - Bundle args = new Bundle(); - if (usedArray.size() > i) { - if (multiNameToSubsMap.containsKey(usedArray.get(i))) { - args.putString("id", multiNameToSubsMap.get(usedArray.get(i))); - } else { - args.putString("id", usedArray.get(i)); - } - } - f.setArguments(args); - return f; - - } else { - Fragment f = new CommentPage(); - Bundle args = new Bundle(); - String name = openingComments.getFullName(); - args.putString("id", name.substring(3)); - args.putBoolean("archived", openingComments.isArchived()); - args.putBoolean( - "contest", openingComments.getDataNode().get("contest_mode").asBoolean()); - args.putBoolean("locked", openingComments.isLocked()); - args.putInt("page", currentComment); - args.putString("subreddit", openingComments.getSubredditName()); - args.putString("baseSubreddit", subToDo); - f.setArguments(args); - return f; - } - } - - @Override - public Parcelable saveState() { - return null; - } - - @Override - public void doSetPrimary(Object object, int position) { - if (position != toOpenComments) { - if (multiNameToSubsMap.containsKey(usedArray.get(position))) { - shouldLoad = multiNameToSubsMap.get(usedArray.get(position)); - } else { - shouldLoad = usedArray.get(position); - } - if (getCurrentFragment() != object) { - mCurrentFragment = ((SubmissionsView) object); - if (mCurrentFragment != null - && mCurrentFragment.posts == null - && mCurrentFragment.isAdded()) { - mCurrentFragment.doAdapter(); - } - } - } else if (object instanceof CommentPage) { - mCurrentComments = (CommentPage) object; - } - } - - public Fragment getCurrentFragment() { - return mCurrentFragment; - } - - @Override - public int getItemPosition(@NonNull Object object) { - if (object != storedFragment) return POSITION_NONE; - return POSITION_UNCHANGED; - } - - @Override - public CharSequence getPageTitle(int position) { - if (usedArray != null && position != toOpenComments) { - return StringUtil.abbreviate(usedArray.get(position), 25); - } else { - return ""; - } - } - } - - private void showUsernameDialog(boolean isMultireddit) { - final Context contextThemeWrapper = new ContextThemeWrapper(MainActivity.this, - new ColorPreferences(MainActivity.this).getFontStyle().getBaseId()); - - // Create TextInputLayout for proper error handling - TextInputLayout inputLayout = new TextInputLayout(contextThemeWrapper); - inputLayout.setErrorIconDrawable(null); // Remove error icon - inputLayout.setErrorEnabled(true); - - final EditText input = new EditText(contextThemeWrapper); - input.setHint(getString(R.string.user_enter)); - input.setHintTextColor(getResources().getColor(R.color.md_grey_700)); - input.setSingleLine(true); // Make input single line - input.setInputType(InputType.TYPE_CLASS_TEXT); // Set input type to text - - // Match search box styling - int underlineColor = new ColorPreferences(contextThemeWrapper).getColor(selectedSub); - input.getBackground().setColorFilter(underlineColor, PorterDuff.Mode.SRC_ATOP); - - // Add EditText to TextInputLayout - inputLayout.addView(input); - - FrameLayout frameLayout = new FrameLayout(contextThemeWrapper); - int padding = (int) TypedValue.applyDimension( - TypedValue.COMPLEX_UNIT_DIP, 24, getResources().getDisplayMetrics()); - frameLayout.setPadding(padding, 0, padding, 0); - frameLayout.addView(inputLayout, new FrameLayout.LayoutParams( - FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.WRAP_CONTENT)); - - int positiveButtonText = R.string.user_btn_goto; - - if (isMultireddit) { - positiveButtonText = R.string.user_btn_gotomultis; - } - - MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(contextThemeWrapper) - .setTitle(R.string.user_enter) - .setView(frameLayout) - .setPositiveButton(positiveButtonText, null) - .setNegativeButton(R.string.btn_cancel, null); - - AlertDialog dialog = builder.create(); - dialog.show(); - - // Set accent color for buttons - int accentColor = new ColorPreferences(contextThemeWrapper).getColor(selectedSub); - dialog.getButton(DialogInterface.BUTTON_POSITIVE).setTextColor(accentColor); - dialog.getButton(DialogInterface.BUTTON_NEGATIVE).setTextColor(accentColor); - - // Set up the positive button click listener after dialog is shown - Button positiveButton = dialog.getButton(DialogInterface.BUTTON_POSITIVE); - positiveButton.setEnabled(true); - positiveButton.setOnClickListener(view1 -> { - String username = input.getText().toString().trim(); - if (username.length() >= 3 && username.length() <= 20) { - Intent inte; - if (isMultireddit) { - inte = new Intent(MainActivity.this, MultiredditOverview.class); - } else { - inte = new Intent(MainActivity.this, Profile.class); - } - inte.putExtra(Profile.EXTRA_PROFILE, username); - MainActivity.this.startActivity(inte); - dialog.dismiss(); - } else { - inputLayout.setError("Username must be between 3 and 20 characters"); - } - }); - - // Clear error when text changes - input.addTextChangedListener(new TextWatcher() { - @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) {} - @Override public void onTextChanged(CharSequence s, int start, int before, int count) { - inputLayout.setError(null); - } - @Override public void afterTextChanged(Editable s) {} - }); - } } diff --git a/app/src/main/java/me/edgan/redditslide/Activities/MainPagerAdapterComment.java b/app/src/main/java/me/edgan/redditslide/Activities/MainPagerAdapterComment.java new file mode 100644 index 000000000..f1d020849 --- /dev/null +++ b/app/src/main/java/me/edgan/redditslide/Activities/MainPagerAdapterComment.java @@ -0,0 +1,190 @@ +package me.edgan.redditslide.Activities; + +import android.os.Bundle; +import android.os.Parcelable; + +import androidx.annotation.NonNull; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; +import androidx.viewpager.widget.ViewPager; + +import java.util.Locale; + +import me.edgan.redditslide.Adapters.SubredditPosts; +import me.edgan.redditslide.Fragments.CommentPage; +import me.edgan.redditslide.Fragments.SubmissionsView; +import me.edgan.redditslide.Visuals.Palette; +import me.edgan.redditslide.util.StringUtil; + + +public class MainPagerAdapterComment extends MainPagerAdapter { + public int size; + public Fragment storedFragment; + CommentPage mCurrentComments; + MainActivity mainActivity; + + public MainPagerAdapterComment(MainActivity mainActivity, FragmentManager fm) { + super(mainActivity, fm); + this.mainActivity = mainActivity; + this.size = mainActivity.usedArray.size(); + mainActivity.pager.clearOnPageChangeListeners(); + mainActivity.pager.addOnPageChangeListener( + new ViewPager.SimpleOnPageChangeListener() { + @Override + public void onPageScrolled( + int position, float positionOffset, int positionOffsetPixels) { + if (positionOffset == 0) { + if (position != mainActivity.toOpenComments) { + mainActivity.pager.setSwipeLeftOnly(true); + mainActivity.header.setBackgroundColor( + Palette.getColor(mainActivity.usedArray.get(position))); + mainActivity.doPageSelectedComments(position); + if (position == mainActivity.toOpenComments - 1 + && mainActivity.adapter != null + && mainActivity.adapter.getCurrentFragment() != null) { + SubmissionsView page = + (SubmissionsView) mainActivity.adapter.getCurrentFragment(); + if (page != null && page.adapter != null) { + page.adapter.refreshView(); + } + } + } else { + if (mainActivity.mAsyncGetSubreddit != null) { + mainActivity.mAsyncGetSubreddit.cancel(true); + } + + if (mainActivity.header.getTranslationY() == 0) { + mainActivity.header.animate() + .translationY(-mainActivity.header.getHeight() * 1.5f) + .setInterpolator(new android.view.animation.LinearInterpolator()) + .setDuration(180); + } + mainActivity.pager.setSwipeLeftOnly(true); + mainActivity.themeSystemBars( + mainActivity.openingComments + .getSubredditName() + .toLowerCase(Locale.ENGLISH)); + mainActivity.setRecentBar( + mainActivity.openingComments + .getSubredditName() + .toLowerCase(Locale.ENGLISH)); + } + } + } + + @Override + public void onPageSelected(final int position) { + if (position == mainActivity.toOpenComments - 1 + && mainActivity.adapter != null + && mainActivity.adapter.getCurrentFragment() != null) { + SubmissionsView page = + (SubmissionsView) mainActivity.adapter.getCurrentFragment(); + if (page != null && page.adapter != null) { + page.adapter.refreshView(); + SubredditPosts p = page.adapter.dataSet; + if (p.offline && !mainActivity.isRestart) { + p.doMainActivityOffline(mainActivity, p.displayer); + } + } + } else { + SubmissionsView page = + (SubmissionsView) mainActivity.adapter.getCurrentFragment(); + if (page != null && page.adapter != null) { + SubredditPosts p = page.adapter.dataSet; + if (p.offline && !mainActivity.isRestart) { + p.doMainActivityOffline(mainActivity, p.displayer); + } + } + } + } + }); + notifyDataSetChanged(); + } + + @Override + public int getCount() { + if (mainActivity.usedArray == null) { + return 1; + } else { + return size; + } + } + + @NonNull + @Override + public Fragment getItem(int i) { + if (mainActivity.openingComments == null || i != mainActivity.toOpenComments) { + SubmissionsView f = new SubmissionsView(); + Bundle args = new Bundle(); + if (mainActivity.usedArray.size() > i) { + if (mainActivity.multiNameToSubsMap.containsKey(mainActivity.usedArray.get(i))) { + args.putString("id", mainActivity.multiNameToSubsMap.get(mainActivity.usedArray.get(i))); + } else { + args.putString("id", mainActivity.usedArray.get(i)); + } + } + f.setArguments(args); + return f; + + } else { + Fragment f = new CommentPage(); + Bundle args = new Bundle(); + String name = mainActivity.openingComments.getFullName(); + args.putString("id", name.substring(3)); + args.putBoolean("archived", mainActivity.openingComments.isArchived()); + args.putBoolean( + "contest", mainActivity.openingComments.getDataNode().get("contest_mode").asBoolean()); + args.putBoolean("locked", mainActivity.openingComments.isLocked()); + args.putInt("page", mainActivity.currentComment); + args.putString("subreddit", mainActivity.openingComments.getSubredditName()); + args.putString("baseSubreddit", mainActivity.subToDo); + f.setArguments(args); + return f; + } + } + + @Override + public Parcelable saveState() { + return null; + } + + @Override + public void doSetPrimary(Object object, int position) { + if (position != mainActivity.toOpenComments) { + if (mainActivity.multiNameToSubsMap.containsKey(mainActivity.usedArray.get(position))) { + mainActivity.shouldLoad = mainActivity.multiNameToSubsMap.get(mainActivity.usedArray.get(position)); + } else { + mainActivity.shouldLoad = mainActivity.usedArray.get(position); + } + if (getCurrentFragment() != object) { + mCurrentFragment = ((SubmissionsView) object); + if (mCurrentFragment != null + && mCurrentFragment.posts == null + && mCurrentFragment.isAdded()) { + mCurrentFragment.doAdapter(); + } + } + } else if (object instanceof CommentPage) { + mCurrentComments = (CommentPage) object; + } + } + + public Fragment getCurrentFragment() { + return mCurrentFragment; + } + + @Override + public int getItemPosition(@NonNull Object object) { + if (object != storedFragment) return POSITION_NONE; + return POSITION_UNCHANGED; + } + + @Override + public CharSequence getPageTitle(int position) { + if (mainActivity.usedArray != null && position != mainActivity.toOpenComments) { + return StringUtil.abbreviate(mainActivity.usedArray.get(position), 25); + } else { + return ""; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/me/edgan/redditslide/Adapters/SideArrayAdapter.java b/app/src/main/java/me/edgan/redditslide/Adapters/SideArrayAdapter.java index 8687a5c8e..fa083dc59 100644 --- a/app/src/main/java/me/edgan/redditslide/Adapters/SideArrayAdapter.java +++ b/app/src/main/java/me/edgan/redditslide/Adapters/SideArrayAdapter.java @@ -19,6 +19,7 @@ import me.edgan.redditslide.Activities.MainActivity; import me.edgan.redditslide.Activities.SubredditView; +import me.edgan.redditslide.Activities.MainPagerAdapterComment; import me.edgan.redditslide.CaseInsensitiveArrayList; import me.edgan.redditslide.Constants; import me.edgan.redditslide.R; @@ -191,10 +192,10 @@ public void onClick(View view) { } else { if (((MainActivity) getContext()).commentPager && ((MainActivity) getContext()).adapter - instanceof MainActivity.MainPagerAdapterComment) { + instanceof MainPagerAdapterComment) { ((MainActivity) getContext()).openingComments = null; ((MainActivity) getContext()).toOpenComments = -1; - ((MainActivity.MainPagerAdapterComment) + ((MainPagerAdapterComment) ((MainActivity) getContext()).adapter) .size = (((MainActivity) getContext()).usedArray.size() + 1); diff --git a/app/src/main/java/me/edgan/redditslide/Adapters/SubmissionAdapter.java b/app/src/main/java/me/edgan/redditslide/Adapters/SubmissionAdapter.java index 458a981e1..7ae8e1122 100644 --- a/app/src/main/java/me/edgan/redditslide/Adapters/SubmissionAdapter.java +++ b/app/src/main/java/me/edgan/redditslide/Adapters/SubmissionAdapter.java @@ -17,6 +17,7 @@ import me.edgan.redditslide.Activities.CommentsScreen; import me.edgan.redditslide.Activities.MainActivity; import me.edgan.redditslide.Activities.SubredditView; +import me.edgan.redditslide.Activities.MainPagerAdapterComment; import me.edgan.redditslide.Authentication; import me.edgan.redditslide.Fragments.SubmissionsView; import me.edgan.redditslide.R; @@ -235,7 +236,7 @@ public void onSingleClick(View v) { && a.commentPager && a.adapter instanceof - MainActivity.MainPagerAdapterComment) { + MainPagerAdapterComment) { if (a.openingComments != submission) { clicked = holder2.getBindingAdapterPosition(); @@ -243,10 +244,10 @@ public void onSingleClick(View v) { a.toOpenComments = a.pager.getCurrentItem() + 1; a.currentComment = holder.getBindingAdapterPosition() - 1; - ((MainActivity.MainPagerAdapterComment) (a).adapter) + ((MainPagerAdapterComment) (a).adapter) .storedFragment = (a).adapter.getCurrentFragment(); - ((MainActivity.MainPagerAdapterComment) (a).adapter) + ((MainPagerAdapterComment) (a).adapter) .size = a.toOpenComments + 1; try { diff --git a/app/src/main/java/me/edgan/redditslide/Adapters/SubmissionNewsAdapter.java b/app/src/main/java/me/edgan/redditslide/Adapters/SubmissionNewsAdapter.java index 70d6bc9ef..3e9a3053e 100644 --- a/app/src/main/java/me/edgan/redditslide/Adapters/SubmissionNewsAdapter.java +++ b/app/src/main/java/me/edgan/redditslide/Adapters/SubmissionNewsAdapter.java @@ -17,6 +17,7 @@ import me.edgan.redditslide.Activities.CommentsScreen; import me.edgan.redditslide.Activities.MainActivity; import me.edgan.redditslide.Activities.SubredditView; +import me.edgan.redditslide.Activities.MainPagerAdapterComment; import me.edgan.redditslide.Authentication; import me.edgan.redditslide.Fragments.SubmissionsView; import me.edgan.redditslide.R; @@ -187,7 +188,7 @@ public void onSingleClick(View v) { && a.commentPager && a.adapter instanceof - MainActivity.MainPagerAdapterComment) { + MainPagerAdapterComment) { if (a.openingComments != submission) { clicked = holder2.getBindingAdapterPosition(); @@ -195,10 +196,10 @@ public void onSingleClick(View v) { a.toOpenComments = a.pager.getCurrentItem() + 1; a.currentComment = holder.getBindingAdapterPosition() - 1; - ((MainActivity.MainPagerAdapterComment) (a).adapter) + ((MainPagerAdapterComment) (a).adapter) .storedFragment = (a).adapter.getCurrentFragment(); - ((MainActivity.MainPagerAdapterComment) (a).adapter) + ((MainPagerAdapterComment) (a).adapter) .size = a.toOpenComments + 1; try { From 16507396e8ab4215011eccd874acd836c5218d57 Mon Sep 17 00:00:00 2001 From: Nathan Grennan Date: Sun, 30 Mar 2025 20:10:44 -0700 Subject: [PATCH 05/99] Improved formatting in DrawerController.java and MainPagerAdapterComment.java --- .../Activities/DrawerController.java | 662 +++++++----------- .../Activities/MainPagerAdapterComment.java | 26 +- 2 files changed, 258 insertions(+), 430 deletions(-) diff --git a/app/src/main/java/me/edgan/redditslide/Activities/DrawerController.java b/app/src/main/java/me/edgan/redditslide/Activities/DrawerController.java index 6152dd07a..bf1e29689 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/DrawerController.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/DrawerController.java @@ -101,10 +101,7 @@ public void doDrawer() { @Override public void onSingleClick(View view) { if (mainActivity.runAfterLoad == null) { - Intent inte = - new Intent( - mainActivity, - MultiredditOverview.class); + Intent inte = new Intent(mainActivity, MultiredditOverview.class); mainActivity.startActivity(inte); } } @@ -221,24 +218,17 @@ public void onSingleClick(View view) { public void onClick(View v) { if (profStuff.getVisibility() == View.GONE) { expand(profStuff); - header.setContentDescription( - mainActivity.getResources().getString(R.string.btn_collapse)); - AnimatorUtil.flipAnimator( - false, header.findViewById(R.id.headerflip)) - .start(); + header.setContentDescription(mainActivity.getResources().getString(R.string.btn_collapse)); + AnimatorUtil.flipAnimator(false, header.findViewById(R.id.headerflip)).start(); } else { collapse(profStuff); - header.setContentDescription( - mainActivity.getResources().getString(R.string.btn_expand)); - AnimatorUtil.flipAnimator( - true, header.findViewById(R.id.headerflip)) - .start(); + header.setContentDescription(mainActivity.getResources().getString(R.string.btn_expand)); + AnimatorUtil.flipAnimator(true, header.findViewById(R.id.headerflip)).start(); } } }); - for (String s : - Authentication.authentication.getStringSet("accounts", new HashSet())) { + for (String s : Authentication.authentication.getStringSet("accounts", new HashSet())) { if (s.contains(":")) { accounts.put(s.split(":")[0], s.split(":")[1]); } else { @@ -250,9 +240,7 @@ public void onClick(View v) { final LinearLayout accountList = header.findViewById(R.id.accountsarea); for (final String accName : keys) { LogUtil.v(accName); - final View t = - mainActivity.getLayoutInflater() - .inflate(R.layout.account_textview_white, accountList, false); + final View t = mainActivity.getLayoutInflater().inflate(R.layout.account_textview_white, accountList, false); ((TextView) t.findViewById(R.id.name)).setText(accName); LogUtil.v("Adding click to " + ((TextView) t.findViewById(R.id.name)).getText()); t.findViewById(R.id.remove) @@ -260,7 +248,6 @@ public void onClick(View v) { new View.OnClickListener() { @Override public void onClick(View v) { - final Context contextThemeWrapper = new ContextThemeWrapper(mainActivity, new ColorPreferences(mainActivity).getFontStyle().getBaseId()); @@ -270,113 +257,74 @@ public void onClick(View v) { .setNegativeButton( R.string.btn_delete, (dialog2, which2) -> { - Set accounts2 = - Authentication.authentication - .getStringSet( - "accounts", - new HashSet<>()); + Set accounts2 = Authentication.authentication.getStringSet("accounts", new HashSet<>()); Set done = new HashSet<>(); + for (String s : accounts2) { if (!s.contains(accName)) { done.add(s); } } + Authentication.authentication .edit() .putStringSet("accounts", done) .commit(); dialog2.dismiss(); accountList.removeView(t); - if (accName.equalsIgnoreCase( - Authentication.name)) { - + if (accName.equalsIgnoreCase(Authentication.name)) { boolean d = false; - for (String s : keys) { + for (String s : keys) { if (!s.equalsIgnoreCase( accName)) { d = true; - LogUtil.v( - "Switching to " - + s); - for (Map.Entry< - String, - String> - e : - accounts - .entrySet()) { - LogUtil.v( - e.getKey() - + ":" - + e - .getValue()); + LogUtil.v("Switching to "+ s); + + for (Map.Entry e : accounts.entrySet()) { + LogUtil.v(e.getKey() + ":" + e.getValue()); } - if (accounts.containsKey(s) - && !accounts.get(s) - .isEmpty()) { - Authentication - .authentication - .edit() - .putString( - "lasttoken", - accounts - .get( - s)) - .remove( - "backedCreds") - .commit(); + + if (accounts.containsKey(s) && !accounts.get(s).isEmpty()) { + Authentication.authentication + .edit() + .putString("lasttoken", accounts.get(s)) + .remove("backedCreds") + .commit(); } else { - ArrayList - tokens = - new ArrayList<>( - Authentication - .authentication - .getStringSet( - "tokens", - new HashSet<>())); - int index = - keys.indexOf(s); - if (keys.indexOf(s) - > tokens - .size()) { + ArrayListtokens = new ArrayList<>(Authentication.authentication + .getStringSet("tokens", new HashSet<>())); + + int index = keys.indexOf(s); + + if (keys.indexOf(s) > tokens.size()) { index -= 1; } - Authentication - .authentication - .edit() - .putString( - "lasttoken", - tokens - .get( - index)) - .remove( - "backedCreds") - .commit(); + + Authentication.authentication + .edit() + .putString("lasttoken", tokens.get(index)) + .remove("backedCreds") + .commit(); } + Authentication.name = s; - UserSubscriptions - .switchAccounts(); - Reddit.forceRestart( - mainActivity, - true); + UserSubscriptions.switchAccounts(); + Reddit.forceRestart(mainActivity, true); break; } } + if (!d) { - Authentication.name = - "LOGGEDOUT"; - Authentication.isLoggedIn = - false; + Authentication.name = "LOGGEDOUT"; + Authentication.isLoggedIn = false; Authentication.authentication - .edit() - .remove("lasttoken") - .remove("backedCreds") - .commit(); - UserSubscriptions - .switchAccounts(); - Reddit.forceRestart( - mainActivity, - true); + .edit() + .remove("lasttoken") + .remove("backedCreds") + .commit(); + UserSubscriptions.switchAccounts(); + Reddit.forceRestart(mainActivity, true); } } else { @@ -392,35 +340,29 @@ public void onClick(View v) { new View.OnClickListener() { @Override public void onClick(View v) { - String accName = - ((TextView) t.findViewById(R.id.name)).getText().toString(); + String accName = ((TextView) t.findViewById(R.id.name)).getText().toString(); LogUtil.v("Found name is " + accName); + if (!accName.equalsIgnoreCase(Authentication.name)) { LogUtil.v("Switching to " + accName); if (!accounts.get(accName).isEmpty()) { LogUtil.v("Using token " + accounts.get(accName)); Authentication.authentication - .edit() - .putString("lasttoken", accounts.get(accName)) - .remove("backedCreds") - .apply(); + .edit() + .putString("lasttoken", accounts.get(accName)) + .remove("backedCreds") + .apply(); } else { - ArrayList tokens = - new ArrayList<>( - Authentication.authentication.getStringSet( - "tokens", new HashSet())); + ArrayList tokens = new ArrayList<>(Authentication.authentication.getStringSet("tokens", new HashSet())); Authentication.authentication - .edit() - .putString( - "lasttoken", - tokens.get(keys.indexOf(accName))) - .remove("backedCreds") - .apply(); + .edit() + .putString("lasttoken", tokens.get(keys.indexOf(accName))) + .remove("backedCreds") + .apply(); } - Authentication.name = accName; + Authentication.name = accName; UserSubscriptions.switchAccounts(); - Reddit.forceRestart(mainActivity, true); } } @@ -437,17 +379,11 @@ public void onClick(View view) { if (body.getVisibility() == View.GONE) { expand(body); AnimatorUtil.flipAnimator(false, view).start(); - view.findViewById(R.id.godown) - .setContentDescription( - mainActivity.getResources() - .getString(R.string.btn_collapse)); + view.findViewById(R.id.godown).setContentDescription(mainActivity.getResources().getString(R.string.btn_collapse)); } else { collapse(body); AnimatorUtil.flipAnimator(true, view).start(); - view.findViewById(R.id.godown) - .setContentDescription( - mainActivity.getResources() - .getString(R.string.btn_expand)); + view.findViewById(R.id.godown).setContentDescription(mainActivity.getResources().getString(R.string.btn_expand)); } } }); @@ -460,10 +396,10 @@ public void onSingleClick(View v) { Authentication.name = "LOGGEDOUT"; Authentication.isLoggedIn = false; Authentication.authentication - .edit() - .remove("lasttoken") - .remove("backedCreds") - .apply(); + .edit() + .remove("lasttoken") + .remove("backedCreds") + .apply(); UserSubscriptions.switchAccounts(); Reddit.forceRestart(mainActivity, true); } @@ -484,9 +420,9 @@ public void onSingleClick(View view) { @Override public void onSingleClick(View view) { Reddit.appRestart - .edit() - .putBoolean("forceoffline", true) - .commit(); + .edit() + .putBoolean("forceoffline", true) + .commit(); Reddit.forceRestart(mainActivity, false); } }); @@ -521,152 +457,106 @@ public void onSingleClick(View view) { public void onClick(View v) { if (profStuff.getVisibility() == View.GONE) { expand(profStuff); - AnimatorUtil.flipAnimator( - false, header.findViewById(R.id.headerflip)) - .start(); - header.findViewById(R.id.headerflip) - .setContentDescription( - mainActivity.getResources() - .getString(R.string.btn_collapse)); + AnimatorUtil.flipAnimator(false, header.findViewById(R.id.headerflip)).start(); + header.findViewById(R.id.headerflip).setContentDescription(mainActivity.getResources().getString(R.string.btn_collapse)); } else { collapse(profStuff); - AnimatorUtil.flipAnimator( - true, header.findViewById(R.id.headerflip)) - .start(); - header.findViewById(R.id.headerflip) - .setContentDescription( - mainActivity.getResources() - .getString(R.string.btn_expand)); + AnimatorUtil.flipAnimator(true, header.findViewById(R.id.headerflip)).start(); + header.findViewById(R.id.headerflip).setContentDescription(mainActivity.getResources().getString(R.string.btn_expand)); } } }); final HashMap accounts = new HashMap<>(); - for (String s : - Authentication.authentication.getStringSet("accounts", new HashSet())) { + for (String s : Authentication.authentication.getStringSet("accounts", new HashSet())) { if (s.contains(":")) { accounts.put(s.split(":")[0], s.split(":")[1]); } else { accounts.put(s, ""); } } - final ArrayList keys = new ArrayList<>(accounts.keySet()); + final ArrayList keys = new ArrayList<>(accounts.keySet()); final LinearLayout accountList = header.findViewById(R.id.accountsarea); + for (final String accName : keys) { LogUtil.v(accName); - final View t = - mainActivity.getLayoutInflater() - .inflate(R.layout.account_textview_white, accountList, false); + final View t = mainActivity.getLayoutInflater().inflate(R.layout.account_textview_white, accountList, false); ((TextView) t.findViewById(R.id.name)).setText(accName); t.findViewById(R.id.remove) .setOnClickListener( new View.OnClickListener() { @Override public void onClick(View v) { - new AlertDialog.Builder(mainActivity) - .setTitle(R.string.profile_remove) - .setMessage(R.string.profile_remove_account) - .setNegativeButton( - R.string.btn_delete, - (dialog2, which2) -> { - Set accounts2 = - Authentication.authentication - .getStringSet( - "accounts", - new HashSet<>()); - Set done = new HashSet<>(); - for (String s : accounts2) { - if (!s.contains(accName)) { - done.add(s); - } + .setTitle(R.string.profile_remove) + .setMessage(R.string.profile_remove_account) + .setNegativeButton( + R.string.btn_delete, + (dialog2, which2) -> { + Set accounts2 = Authentication.authentication.getStringSet("accounts", new HashSet<>()); + Set done = new HashSet<>(); + + for (String s : accounts2) { + if (!s.contains(accName)) { + done.add(s); } - Authentication.authentication - .edit() - .putStringSet("accounts", done) - .commit(); - dialog2.dismiss(); - accountList.removeView(t); - - if (accName.equalsIgnoreCase( - Authentication.name)) { - boolean d = false; - for (String s : keys) { - if (!s.equalsIgnoreCase( - accName)) { - d = true; - LogUtil.v( - "Switching to " - + s); - if (!accounts.get(s) - .isEmpty()) { - Authentication - .authentication - .edit() - .putString( - "lasttoken", - accounts - .get( - s)) - .remove( - "backedCreds") - .commit(); + } - } else { - ArrayList - tokens = - new ArrayList<>( - Authentication - .authentication - .getStringSet( - "tokens", - new HashSet<>())); - Authentication - .authentication - .edit() - .putString( - "lasttoken", - tokens - .get( - keys - .indexOf( - s))) - .remove( - "backedCreds") - .commit(); - } - Authentication.name = s; - UserSubscriptions - .switchAccounts(); - Reddit.forceRestart( - mainActivity, - true); - } - } - if (!d) { - Authentication.name = - "LOGGEDOUT"; - Authentication.isLoggedIn = - false; - Authentication.authentication + Authentication.authentication.edit().putStringSet("accounts", done).commit(); + dialog2.dismiss(); + accountList.removeView(t); + + if (accName.equalsIgnoreCase(Authentication.name)) { + boolean d = false; + + for (String s : keys) { + if (!s.equalsIgnoreCase(accName)) { + d = true; + LogUtil.v("Switching to " + s); + if (!accounts.get(s).isEmpty()) { + Authentication.authentication + .edit() + .putString("lasttoken", accounts.get(s)) + .remove("backedCreds") + .commit(); + } else { + ArrayListtokens = new ArrayList<>(Authentication.authentication + .getStringSet("tokens", new HashSet<>())); + Authentication.authentication .edit() - .remove("lasttoken") + .putString("lasttoken", tokens.get(keys.indexOf(s))) .remove("backedCreds") .commit(); - UserSubscriptions - .switchAccounts(); - Reddit.forceRestart( - mainActivity, - true); + } + + Authentication.name = s; + UserSubscriptions.switchAccounts(); + Reddit.forceRestart(mainActivity, true); } - } else { - accounts.remove(accName); - keys.remove(accName); } - }) - .setPositiveButton(R.string.btn_cancel, null) - .show(); + + if (!d) { + Authentication.name = "LOGGEDOUT"; + Authentication.isLoggedIn = false; + Authentication.authentication + .edit() + .remove("lasttoken") + .remove("backedCreds") + .commit(); + UserSubscriptions + .switchAccounts(); + Reddit.forceRestart( + mainActivity, + true); + } + } else { + accounts.remove(accName); + keys.remove(accName); + } + }) + .setPositiveButton(R.string.btn_cancel, null) + .show(); } }); t.setOnClickListener( @@ -676,31 +566,23 @@ public void onSingleClick(View v) { if (!accName.equalsIgnoreCase(Authentication.name)) { if (!accounts.get(accName).isEmpty()) { Authentication.authentication - .edit() - .putString("lasttoken", accounts.get(accName)) - .remove("backedCreds") - .commit(); + .edit() + .putString("lasttoken", accounts.get(accName)) + .remove("backedCreds") + .commit(); } else { - ArrayList tokens = - new ArrayList<>( - Authentication.authentication.getStringSet( - "tokens", new HashSet())); + ArrayList tokens = new ArrayList<>(Authentication.authentication.getStringSet("tokens", new HashSet())); Authentication.authentication - .edit() - .putString( - "lasttoken", - tokens.get(keys.indexOf(accName))) - .remove("backedCreds") - .commit(); + .edit() + .putString("lasttoken", tokens.get(keys.indexOf(accName))) + .remove("backedCreds") + .commit(); } // Removing this will break Guest mode Authentication.isLoggedIn = true; - Authentication.name = accName; - UserSubscriptions.switchAccounts(); - Reddit.forceRestart(mainActivity, true); } } @@ -723,9 +605,9 @@ public void onSingleClick(View view) { @Override public void onSingleClick(View view) { Reddit.appRestart - .edit() - .putBoolean("forceoffline", true) - .commit(); + .edit() + .putBoolean("forceoffline", true) + .commit(); Reddit.forceRestart(mainActivity, false); } }); @@ -737,54 +619,40 @@ public void onSingleClick(View view) { @Override public void onClick(View view) { new MaterialDialog.Builder(mainActivity) - .inputRange(3, 20) - .alwaysCallInputCallback() - .input( - mainActivity.getString(R.string.user_enter), - null, - new MaterialDialog.InputCallback() { - @Override - public void onInput( - @NonNull MaterialDialog dialog, - CharSequence input) { - final EditText editText = - dialog.getInputEditText(); - EditTextValidator.validateUsername( - editText); - if (input.length() >= 3 - && input.length() <= 20) { - dialog.getActionButton( - DialogAction - .POSITIVE) - .setEnabled(true); - } + .inputRange(3, 20) + .alwaysCallInputCallback() + .input( + mainActivity.getString(R.string.user_enter), + null, + new MaterialDialog.InputCallback() { + @Override + public void onInput( + @NonNull MaterialDialog dialog, + CharSequence input) { + final EditText editText = dialog.getInputEditText(); + EditTextValidator.validateUsername(editText); + if (input.length() >= 3 && input.length() <= 20) { + dialog.getActionButton(DialogAction.POSITIVE).setEnabled(true); } - }) - .positiveText(R.string.user_btn_gotomultis) - .onPositive( - new MaterialDialog.SingleButtonCallback() { - @Override - public void onClick( - @NonNull MaterialDialog dialog, - @NonNull DialogAction which) { - if (mainActivity.runAfterLoad == null) { - Intent inte = - new Intent( - mainActivity, - MultiredditOverview - .class); - inte.putExtra( - Profile.EXTRA_PROFILE, - dialog.getInputEditText() - .getText() - .toString()); - mainActivity.startActivity( - inte); - } + } + }) + .positiveText(R.string.user_btn_gotomultis) + .onPositive( + new MaterialDialog.SingleButtonCallback() { + @Override + public void onClick( + @NonNull MaterialDialog dialog, + @NonNull DialogAction which) { + + if (mainActivity.runAfterLoad == null) { + Intent inte = new Intent(mainActivity, MultiredditOverview.class); + inte.putExtra(Profile.EXTRA_PROFILE, dialog.getInputEditText().getText().toString()); + mainActivity.startActivity(inte); } - }) - .negativeText(R.string.btn_cancel) - .show(); + } + }) + .negativeText(R.string.btn_cancel) + .show(); } }); @@ -813,40 +681,30 @@ public void onSingleClick(View view) { public void onClick(View v) { if (expandSettings.getVisibility() == View.GONE) { expand(expandSettings); - header.findViewById(R.id.godown_settings) - .setContentDescription( - mainActivity.getResources() - .getString(R.string.btn_collapse)); + header.findViewById(R.id.godown_settings).setContentDescription(mainActivity.getResources().getString(R.string.btn_collapse)); AnimatorUtil.flipAnimator(false, v).start(); } else { collapse(expandSettings); - header.findViewById(R.id.godown_settings) - .setContentDescription( - mainActivity.getResources().getString(R.string.btn_expand)); + header.findViewById(R.id.godown_settings).setContentDescription(mainActivity.getResources().getString(R.string.btn_expand)); AnimatorUtil.flipAnimator(true, v).start(); } } }); { // Set up quick setting toggles - final SwitchCompat toggleNightMode = - expandSettings.findViewById(R.id.toggle_night_mode); + final SwitchCompat toggleNightMode = expandSettings.findViewById(R.id.toggle_night_mode); toggleNightMode.setVisibility(View.VISIBLE); toggleNightMode.setChecked(mainActivity.inNightMode); toggleNightMode.setOnCheckedChangeListener( new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - SettingValues.forcedNightModeState = - isChecked - ? SettingValues.ForcedState.FORCED_ON - : SettingValues.ForcedState.FORCED_OFF; + SettingValues.forcedNightModeState = isChecked ? SettingValues.ForcedState.FORCED_ON : SettingValues.ForcedState.FORCED_OFF; mainActivity.restartTheme(); } }); - final SwitchCompat toggleImmersiveMode = - expandSettings.findViewById(R.id.toggle_immersive_mode); + final SwitchCompat toggleImmersiveMode = expandSettings.findViewById(R.id.toggle_immersive_mode); toggleImmersiveMode.setChecked(SettingValues.immersiveMode); toggleImmersiveMode.setOnCheckedChangeListener( new CompoundButton.OnCheckedChangeListener() { @@ -854,9 +712,10 @@ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { SettingValues.immersiveMode = isChecked; SettingValues.prefs - .edit() - .putBoolean(SettingValues.PREF_IMMERSIVE_MODE, isChecked) - .apply(); + .edit() + .putBoolean(SettingValues.PREF_IMMERSIVE_MODE, isChecked) + .apply(); + if (isChecked) { mainActivity.hideDecor(); } else { @@ -873,15 +732,14 @@ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { SettingValues.showNSFWContent = isChecked; SettingValues.prefs - .edit() - .putBoolean(SettingValues.PREF_SHOW_NSFW_CONTENT, isChecked) - .apply(); + .edit() + .putBoolean(SettingValues.PREF_SHOW_NSFW_CONTENT, isChecked) + .apply(); mainActivity.reloadSubs(); } }); - final SwitchCompat toggleRightThumbnails = - expandSettings.findViewById(R.id.toggle_right_thumbnails); + final SwitchCompat toggleRightThumbnails = expandSettings.findViewById(R.id.toggle_right_thumbnails); toggleRightThumbnails.setChecked(SettingValues.switchThumb); toggleRightThumbnails.setOnCheckedChangeListener( new CompoundButton.OnCheckedChangeListener() { @@ -889,15 +747,14 @@ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { SettingValues.switchThumb = isChecked; SettingValues.prefs - .edit() - .putBoolean(SettingValues.PREF_SWITCH_THUMB, isChecked) - .apply(); + .edit() + .putBoolean(SettingValues.PREF_SWITCH_THUMB, isChecked) + .apply(); mainActivity.reloadSubs(); } }); - final SwitchCompat toggleReaderMode = - expandSettings.findViewById(R.id.toggle_reader_mode); + final SwitchCompat toggleReaderMode = expandSettings.findViewById(R.id.toggle_reader_mode); toggleReaderMode.setChecked(SettingValues.readerMode); toggleReaderMode.setOnCheckedChangeListener( new CompoundButton.OnCheckedChangeListener() { @@ -905,9 +762,9 @@ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { SettingValues.readerMode = isChecked; SettingValues.prefs - .edit() - .putBoolean(SettingValues.PREF_READER_MODE, isChecked) - .apply(); + .edit() + .putBoolean(SettingValues.PREF_READER_MODE, isChecked) + .apply(); } }); } @@ -917,8 +774,7 @@ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { new OnSingleClickListener() { @Override public void onSingleClick(View view) { - Intent i = - new Intent(mainActivity, ManageOfflineContent.class); + Intent i = new Intent(mainActivity, ManageOfflineContent.class); mainActivity.startActivity(i); } }); @@ -943,8 +799,7 @@ public void onSingleClick(View view) { public void onSingleClick(View v) { Intent i = new Intent(mainActivity, SettingsActivity.class); mainActivity.startActivity(i); - // Cancel sub loading because exiting the settings will reload it - // anyway + // Cancel sub loading because exiting the settings will reload it anyway if (mainActivity.mAsyncGetSubreddit != null) mainActivity.mAsyncGetSubreddit.cancel(true); mainActivity.drawerLayout.closeDrawers(); } @@ -953,12 +808,7 @@ public void onSingleClick(View v) { final Toolbar toolbar = (Toolbar) mainActivity.findViewById(R.id.toolbar); final androidx.appcompat.app.ActionBarDrawerToggle actionBarDrawerToggle = - new androidx.appcompat.app.ActionBarDrawerToggle( - mainActivity, - mainActivity.drawerLayout, - toolbar, - R.string.btn_open, - R.string.btn_close) { + new androidx.appcompat.app.ActionBarDrawerToggle(mainActivity, mainActivity.drawerLayout, toolbar, R.string.btn_open, R.string.btn_close) { @Override public void onDrawerSlide(View drawerView, float slideOffset) { super.onDrawerSlide(drawerView, 0); // this disables the animation @@ -973,10 +823,10 @@ public void onDrawerOpened(View drawerView) { if (current == mainActivity.toOpenComments && mainActivity.toOpenComments != 0) { current -= 1; } + String compare = mainActivity.usedArray.get(current); - if (compare.equals("random") - || compare.equals("myrandom") - || compare.equals("randnsfw")) { + + if (compare.equals("random") || compare.equals("myrandom") || compare.equals("randnsfw")) { if (mainActivity.adapter != null && mainActivity.adapter.getCurrentFragment() != null && ((SubmissionsView) mainActivity.adapter.getCurrentFragment()) @@ -1001,8 +851,7 @@ public void onDrawerOpened(View drawerView) { @Override public void onDrawerClosed(View view) { super.onDrawerClosed(view); - KeyboardUtil.hideKeyboard( - mainActivity, mainActivity.drawerLayout.getWindowToken(), 0); + KeyboardUtil.hideKeyboard(mainActivity, mainActivity.drawerLayout.getWindowToken(), 0); } }; @@ -1011,6 +860,7 @@ public void onDrawerClosed(View view) { actionBarDrawerToggle.syncState(); header.findViewById(R.id.back).setBackgroundColor(Palette.getColor("alsdkfjasld")); accountsArea = header.findViewById(R.id.accountsarea); + if (accountsArea != null) { accountsArea.setBackgroundColor(Palette.getDarkerColor("alsdkfjasld")); } @@ -1020,19 +870,16 @@ public void onDrawerClosed(View view) { } public void hideDrawerItems() { - for (DrawerItemsDialog.SettingsDrawerEnum settingDrawerItem : - DrawerItemsDialog.SettingsDrawerEnum.values()) { + for (DrawerItemsDialog.SettingsDrawerEnum settingDrawerItem : DrawerItemsDialog.SettingsDrawerEnum.values()) { View drawerItem = drawerSubList.findViewById(settingDrawerItem.drawerId); - if (drawerItem != null - && drawerItem.getVisibility() == View.VISIBLE - && (SettingValues.selectedDrawerItems & settingDrawerItem.value) == 0) { + + if (drawerItem != null && drawerItem.getVisibility() == View.VISIBLE && (SettingValues.selectedDrawerItems & settingDrawerItem.value) == 0) { drawerItem.setVisibility(View.GONE); } } } public void setDrawerSubList() { - ArrayList copy; if (NetworkUtil.isConnected(mainActivity)) { @@ -1043,26 +890,23 @@ public void setDrawerSubList() { copy.removeAll(Arrays.asList("", null)); - sideArrayAdapter = - new SideArrayAdapter( - mainActivity, copy, UserSubscriptions.getAllSubreddits(mainActivity), drawerSubList); + sideArrayAdapter = new SideArrayAdapter(mainActivity, copy, UserSubscriptions.getAllSubreddits(mainActivity), drawerSubList); drawerSubList.setAdapter(sideArrayAdapter); if ((SettingValues.subredditSearchMethod != Constants.SUBREDDIT_SEARCH_METHOD_TOOLBAR)) { drawerSearch = mainActivity.headerMain.findViewById(R.id.sort); drawerSearch.setVisibility(View.VISIBLE); - drawerSubList.setFocusable(false); mainActivity.headerMain - .findViewById(R.id.close_search_drawer) - .setOnClickListener( - new View.OnClickListener() { - @Override - public void onClick(View v) { - drawerSearch.setText(""); - } - }); + .findViewById(R.id.close_search_drawer) + .setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View v) { + drawerSearch.setText(""); + } + }); drawerSearch.setOnFocusChangeListener( new View.OnFocusChangeListener() { @@ -1091,29 +935,24 @@ public boolean onEditorAction(TextView arg0, int arg1, KeyEvent arg2) { if (arg1 == EditorInfo.IME_ACTION_SEARCH) { // If it the input text doesn't match a subreddit from the list exactly, openInSubView is true - if (sideArrayAdapter.fitems == null - || sideArrayAdapter.openInSubView - || !searchSubFound) { - Intent inte = - new Intent(mainActivity, SubredditView.class); - inte.putExtra( - SubredditView.EXTRA_SUBREDDIT, - searchText); + if (sideArrayAdapter.fitems == null || sideArrayAdapter.openInSubView || !searchSubFound) { + Intent inte = new Intent(mainActivity, SubredditView.class); + inte.putExtra(SubredditView.EXTRA_SUBREDDIT, searchText); mainActivity.startActivityForResult(inte, 2001); } else { - if (mainActivity.commentPager - && mainActivity.adapter instanceof MainPagerAdapterComment) { + if (mainActivity.commentPager && mainActivity.adapter instanceof MainPagerAdapterComment) { mainActivity.openingComments = null; mainActivity.toOpenComments = -1; - ((MainPagerAdapterComment) mainActivity.adapter).size = - (mainActivity.usedArray.size() + 1); + ((MainPagerAdapterComment) mainActivity.adapter).size = (mainActivity.usedArray.size() + 1); mainActivity.adapter.notifyDataSetChanged(); + if (!searchSubFound) { mainActivity.doPageSelectedComments(sideArrayAdapterIndex); } else { mainActivity.doPageSelectedComments(searchSubIndex); } } + if (!searchSubFound) { mainActivity.pager.setCurrentItem(sideArrayAdapterIndex); } else { @@ -1123,6 +962,7 @@ public boolean onEditorAction(TextView arg0, int arg1, KeyEvent arg2) { mainActivity.drawerLayout.closeDrawers(); drawerSearch.setText(""); View view = mainActivity.getCurrentFocus(); + if (view != null) { KeyboardUtil.hideKeyboard( mainActivity, view.getWindowToken(), 0); @@ -1137,18 +977,18 @@ public boolean onEditorAction(TextView arg0, int arg1, KeyEvent arg2) { close.setVisibility(View.GONE); drawerSearch.addTextChangedListener( - new SimpleTextWatcher() { - @Override - public void afterTextChanged(Editable editable) { - final String result = editable.toString(); - if (result.isEmpty()) { - close.setVisibility(View.GONE); - } else { - close.setVisibility(View.VISIBLE); - } - sideArrayAdapter.getFilter().filter(result); + new SimpleTextWatcher() { + @Override + public void afterTextChanged(Editable editable) { + final String result = editable.toString(); + if (result.isEmpty()) { + close.setVisibility(View.GONE); + } else { + close.setVisibility(View.VISIBLE); } - }); + sideArrayAdapter.getFilter().filter(result); + } + }); } else { if (drawerSearch != null) { drawerSearch.setOnClickListener(null); @@ -1159,7 +999,6 @@ public void afterTextChanged(Editable editable) { private void collapse(final LinearLayout v) { int finalHeight = v.getHeight(); - ValueAnimator mAnimator = AnimatorUtil.slideAnimator(finalHeight, 0, v); mAnimator.addListener( @@ -1173,11 +1012,11 @@ public void onAnimationEnd(Animator animator) { } private void expand(LinearLayout v) { - // set Visible v.setVisibility(View.VISIBLE); final int widthSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); final int heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); + v.measure(widthSpec, heightSpec); ValueAnimator mAnimator = AnimatorUtil.slideAnimator(0, v.getMeasuredHeight(), v); @@ -1185,8 +1024,7 @@ private void expand(LinearLayout v) { } private void showUsernameDialog(boolean isMultireddit) { - final Context contextThemeWrapper = new ContextThemeWrapper(mainActivity, - new ColorPreferences(mainActivity).getFontStyle().getBaseId()); + final Context contextThemeWrapper = new ContextThemeWrapper(mainActivity, new ColorPreferences(mainActivity).getFontStyle().getBaseId()); // Create TextInputLayout for proper error handling TextInputLayout inputLayout = new TextInputLayout(contextThemeWrapper); @@ -1207,11 +1045,9 @@ private void showUsernameDialog(boolean isMultireddit) { inputLayout.addView(input); FrameLayout frameLayout = new FrameLayout(contextThemeWrapper); - int padding = (int) TypedValue.applyDimension( - TypedValue.COMPLEX_UNIT_DIP, 24, mainActivity.getResources().getDisplayMetrics()); + int padding = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 24, mainActivity.getResources().getDisplayMetrics()); frameLayout.setPadding(padding, 0, padding, 0); - frameLayout.addView(inputLayout, new FrameLayout.LayoutParams( - FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.WRAP_CONTENT)); + frameLayout.addView(inputLayout, new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.WRAP_CONTENT)); int positiveButtonText = R.string.user_btn_goto; @@ -1220,10 +1056,10 @@ private void showUsernameDialog(boolean isMultireddit) { } MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(contextThemeWrapper) - .setTitle(R.string.user_enter) - .setView(frameLayout) - .setPositiveButton(positiveButtonText, null) - .setNegativeButton(R.string.btn_cancel, null); + .setTitle(R.string.user_enter) + .setView(frameLayout) + .setPositiveButton(positiveButtonText, null) + .setNegativeButton(R.string.btn_cancel, null); AlertDialog dialog = builder.create(); dialog.show(); @@ -1240,11 +1076,13 @@ private void showUsernameDialog(boolean isMultireddit) { String username = input.getText().toString().trim(); if (username.length() >= 3 && username.length() <= 20) { Intent inte; + if (isMultireddit) { inte = new Intent(mainActivity, MultiredditOverview.class); } else { inte = new Intent(mainActivity, Profile.class); } + inte.putExtra(Profile.EXTRA_PROFILE, username); mainActivity.startActivity(inte); dialog.dismiss(); diff --git a/app/src/main/java/me/edgan/redditslide/Activities/MainPagerAdapterComment.java b/app/src/main/java/me/edgan/redditslide/Activities/MainPagerAdapterComment.java index f1d020849..5c925343d 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/MainPagerAdapterComment.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/MainPagerAdapterComment.java @@ -39,11 +39,9 @@ public void onPageScrolled( mainActivity.header.setBackgroundColor( Palette.getColor(mainActivity.usedArray.get(position))); mainActivity.doPageSelectedComments(position); - if (position == mainActivity.toOpenComments - 1 - && mainActivity.adapter != null - && mainActivity.adapter.getCurrentFragment() != null) { - SubmissionsView page = - (SubmissionsView) mainActivity.adapter.getCurrentFragment(); + if (position == mainActivity.toOpenComments - 1 && mainActivity.adapter != null && mainActivity.adapter.getCurrentFragment() != null) { + SubmissionsView page = (SubmissionsView) mainActivity.adapter.getCurrentFragment(); + if (page != null && page.adapter != null) { page.adapter.refreshView(); } @@ -59,15 +57,10 @@ public void onPageScrolled( .setInterpolator(new android.view.animation.LinearInterpolator()) .setDuration(180); } + mainActivity.pager.setSwipeLeftOnly(true); - mainActivity.themeSystemBars( - mainActivity.openingComments - .getSubredditName() - .toLowerCase(Locale.ENGLISH)); - mainActivity.setRecentBar( - mainActivity.openingComments - .getSubredditName() - .toLowerCase(Locale.ENGLISH)); + mainActivity.themeSystemBars(mainActivity.openingComments.getSubredditName().toLowerCase(Locale.ENGLISH)); + mainActivity.setRecentBar(mainActivity.openingComments.getSubredditName().toLowerCase(Locale.ENGLISH)); } } } @@ -132,8 +125,7 @@ public Fragment getItem(int i) { String name = mainActivity.openingComments.getFullName(); args.putString("id", name.substring(3)); args.putBoolean("archived", mainActivity.openingComments.isArchived()); - args.putBoolean( - "contest", mainActivity.openingComments.getDataNode().get("contest_mode").asBoolean()); + args.putBoolean("contest", mainActivity.openingComments.getDataNode().get("contest_mode").asBoolean()); args.putBoolean("locked", mainActivity.openingComments.isLocked()); args.putInt("page", mainActivity.currentComment); args.putString("subreddit", mainActivity.openingComments.getSubredditName()); @@ -158,9 +150,7 @@ public void doSetPrimary(Object object, int position) { } if (getCurrentFragment() != object) { mCurrentFragment = ((SubmissionsView) object); - if (mCurrentFragment != null - && mCurrentFragment.posts == null - && mCurrentFragment.isAdded()) { + if (mCurrentFragment != null && mCurrentFragment.posts == null && mCurrentFragment.isAdded()) { mCurrentFragment.doAdapter(); } } From fc43812c9802826ddb12f16c747a9144432480a1 Mon Sep 17 00:00:00 2001 From: Nathan Grennan Date: Sun, 30 Mar 2025 20:52:27 -0700 Subject: [PATCH 06/99] Broke the ToolbarSearchController class out of MainActivity.java --- .../redditslide/Activities/MainActivity.java | 353 +----------------- .../Activities/ToolbarSearchController.java | 294 +++++++++++++++ .../Adapters/SideArrayAdapter.java | 2 +- 3 files changed, 302 insertions(+), 347 deletions(-) create mode 100644 app/src/main/java/me/edgan/redditslide/Activities/ToolbarSearchController.java diff --git a/app/src/main/java/me/edgan/redditslide/Activities/MainActivity.java b/app/src/main/java/me/edgan/redditslide/Activities/MainActivity.java index 76d07ae3b..4f8fd9f81 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/MainActivity.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/MainActivity.java @@ -38,10 +38,7 @@ import android.view.ViewStub; import android.view.ViewTreeObserver; import android.view.Window; -import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.LinearInterpolator; -import android.view.inputmethod.EditorInfo; -import android.view.inputmethod.InputMethodManager; import android.widget.AutoCompleteTextView; import android.widget.CompoundButton; import android.widget.EditText; @@ -60,7 +57,6 @@ import androidx.appcompat.widget.PopupMenu; import androidx.appcompat.widget.SwitchCompat; import androidx.appcompat.widget.Toolbar; -import androidx.cardview.widget.CardView; import androidx.core.content.pm.ShortcutInfoCompat; import androidx.core.content.pm.ShortcutManagerCompat; import androidx.core.content.res.ResourcesCompat; @@ -122,7 +118,6 @@ import me.edgan.redditslide.util.StringUtil; import me.edgan.redditslide.util.SubmissionParser; import me.edgan.redditslide.util.TimeUtils; -import me.edgan.redditslide.util.stubs.SimpleTextWatcher; import me.edgan.redditslide.util.FilterContentUtil; import net.dean.jraw.ApiException; @@ -213,14 +208,14 @@ public class MainActivity extends BaseActivity boolean currentlySubbed; int back; AsyncGetSubredditTask mAsyncGetSubreddit = null; - private int headerHeight; // height of the header + int headerHeight; public int reloadItemNumber = -2; private static final int NOTIFICATION_PERMISSION_REQUEST_CODE = 1001; private View rootView; DrawerController drawerController; - + public ToolbarSearchController toolbarSearchController; @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == SETTINGS_RESULT) { @@ -961,6 +956,7 @@ public void onSingleClick( setContentView(R.layout.activity_overview); drawerController = new DrawerController(this); + toolbarSearchController = new ToolbarSearchController(this); rootView = findViewById(android.R.id.content); @@ -1097,7 +1093,7 @@ protected void onPreExecute() { if (SettingValues.subredditSearchMethod == Constants.SUBREDDIT_SEARCH_METHOD_TOOLBAR || SettingValues.subredditSearchMethod == Constants.SUBREDDIT_SEARCH_METHOD_BOTH) { - setupSubredditSearchToolbar(); + toolbarSearchController.setupSubredditSearchToolbar(); } /** @@ -1195,11 +1191,11 @@ public void onResume() { findViewById(R.id.drawer_divider).setVisibility(View.GONE); } else if (SettingValues.subredditSearchMethod == Constants.SUBREDDIT_SEARCH_METHOD_TOOLBAR) { - setupSubredditSearchToolbar(); + toolbarSearchController.setupSubredditSearchToolbar(); } else if (SettingValues.subredditSearchMethod == Constants.SUBREDDIT_SEARCH_METHOD_BOTH) { findViewById(R.id.drawer_divider).setVisibility(View.GONE); - setupSubredditSearchToolbar(); + toolbarSearchController.setupSubredditSearchToolbar(); drawerController.setDrawerSubList(); } SettingsGeneralFragment.searchChanged = false; @@ -2668,32 +2664,6 @@ public void doSubSidebarNoLoad(final String subreddit) { * @param GO_TO_SUB_FIELD search field in toolbar * @param CLOSE_BUTTON button that clears the search and closes the search UI */ - public void enterAnimationsForToolbarSearch( - final long ANIMATION_DURATION, - final CardView SUGGESTIONS_BACKGROUND, - final AutoCompleteTextView GO_TO_SUB_FIELD, - final ImageView CLOSE_BUTTON) { - SUGGESTIONS_BACKGROUND - .animate() - .translationY(headerHeight) - .setInterpolator(new AccelerateDecelerateInterpolator()) - .setDuration(ANIMATION_DURATION + ANIMATE_DURATION_OFFSET) - .start(); - - GO_TO_SUB_FIELD - .animate() - .alpha(1f) - .setInterpolator(new AccelerateDecelerateInterpolator()) - .setDuration(ANIMATION_DURATION) - .start(); - - CLOSE_BUTTON - .animate() - .alpha(1f) - .setInterpolator(new AccelerateDecelerateInterpolator()) - .setDuration(ANIMATION_DURATION) - .start(); - } /** * Starts the exit animations for various UI components of the toolbar subreddit search @@ -2703,56 +2673,6 @@ public void enterAnimationsForToolbarSearch( * @param GO_TO_SUB_FIELD search field in toolbar * @param CLOSE_BUTTON button that clears the search and closes the search UI */ - public void exitAnimationsForToolbarSearch( - final long ANIMATION_DURATION, - final CardView SUGGESTIONS_BACKGROUND, - final AutoCompleteTextView GO_TO_SUB_FIELD, - final ImageView CLOSE_BUTTON) { - SUGGESTIONS_BACKGROUND - .animate() - .translationY(-SUGGESTIONS_BACKGROUND.getHeight()) - .setInterpolator(new AccelerateDecelerateInterpolator()) - .setDuration(ANIMATION_DURATION + ANIMATE_DURATION_OFFSET) - .start(); - - GO_TO_SUB_FIELD - .animate() - .alpha(0f) - .setInterpolator(new AccelerateDecelerateInterpolator()) - .setDuration(ANIMATION_DURATION) - .start(); - - CLOSE_BUTTON - .animate() - .alpha(0f) - .setInterpolator(new AccelerateDecelerateInterpolator()) - .setDuration(ANIMATION_DURATION) - .start(); - - // Helps smooth the transition between the toolbar title being reset and the search elements - // fading out. - final long OFFSET_ANIM = (ANIMATION_DURATION == 0) ? 0 : ANIMATE_DURATION_OFFSET; - - // Hide the various UI components after the animations are complete and - // reset the toolbar title - new Handler() - .postDelayed( - new Runnable() { - @Override - public void run() { - SUGGESTIONS_BACKGROUND.setVisibility(View.GONE); - GO_TO_SUB_FIELD.setVisibility(View.GONE); - CLOSE_BUTTON.setVisibility(View.GONE); - - if (SettingValues.single) { - getSupportActionBar().setTitle(selectedSub); - } else { - getSupportActionBar().setTitle(tabViewModeTitle); - } - } - }, - ANIMATION_DURATION + ANIMATE_DURATION_OFFSET); - } public int getCurrentPage() { int position = 0; @@ -2993,7 +2913,7 @@ public void reloadSubs() { if (SettingValues.subredditSearchMethod == Constants.SUBREDDIT_SEARCH_METHOD_TOOLBAR || SettingValues.subredditSearchMethod == Constants.SUBREDDIT_SEARCH_METHOD_BOTH) { - setupSubredditSearchToolbar(); + toolbarSearchController.setupSubredditSearchToolbar(); } // When setting tab text, add null check and try-catch @@ -3439,265 +3359,6 @@ private void setViews( * OnLongClickListener needs to be set for the toolbar as well as handling all of the relevant * onClicks for the views of the search bar. */ - private void setupSubredditSearchToolbar() { - if (!NetworkUtil.isConnected(this)) { - if (findViewById(R.id.drawer_divider) != null) { - findViewById(R.id.drawer_divider).setVisibility(View.GONE); - } - } else { - if ((SettingValues.subredditSearchMethod == Constants.SUBREDDIT_SEARCH_METHOD_TOOLBAR - || SettingValues.subredditSearchMethod - == Constants.SUBREDDIT_SEARCH_METHOD_BOTH) - && usedArray != null - && !usedArray.isEmpty()) { - if (findViewById(R.id.drawer_divider) != null) { - if (SettingValues.subredditSearchMethod - == Constants.SUBREDDIT_SEARCH_METHOD_BOTH) { - findViewById(R.id.drawer_divider).setVisibility(View.GONE); - } else { - findViewById(R.id.drawer_divider).setVisibility(View.VISIBLE); - } - } - final ListView TOOLBAR_SEARCH_SUGGEST_LIST = - (ListView) findViewById(R.id.toolbar_search_suggestions_list); - final ArrayList subs_copy = new ArrayList<>(usedArray); - final SideArrayAdapter TOOLBAR_SEARCH_SUGGEST_ADAPTER = - new SideArrayAdapter( - this, - subs_copy, - UserSubscriptions.getAllSubreddits(this), - TOOLBAR_SEARCH_SUGGEST_LIST); - - if (TOOLBAR_SEARCH_SUGGEST_LIST != null) { - TOOLBAR_SEARCH_SUGGEST_LIST.setAdapter(TOOLBAR_SEARCH_SUGGEST_ADAPTER); - } - - if (mToolbar != null) { - mToolbar.setOnLongClickListener( - new View.OnLongClickListener() { - @Override - public boolean onLongClick(View v) { - final AutoCompleteTextView GO_TO_SUB_FIELD = - (AutoCompleteTextView) - findViewById(R.id.toolbar_search); - final ImageView CLOSE_BUTTON = - (ImageView) findViewById(R.id.close_search_toolbar); - final CardView SUGGESTIONS_BACKGROUND = - (CardView) - findViewById(R.id.toolbar_search_suggestions); - - // if the view mode is set to Subreddit Tabs, save the title - // ("Slide" or "Slide (debug)") - tabViewModeTitle = - (!SettingValues.single) - ? getSupportActionBar().getTitle().toString() - : null; - - getSupportActionBar() - .setTitle(""); // clear title to make room for search - // field - - if (GO_TO_SUB_FIELD != null - && CLOSE_BUTTON != null - && SUGGESTIONS_BACKGROUND != null) { - GO_TO_SUB_FIELD.setVisibility(View.VISIBLE); - CLOSE_BUTTON.setVisibility(View.VISIBLE); - SUGGESTIONS_BACKGROUND.setVisibility(View.VISIBLE); - - // run enter animations - enterAnimationsForToolbarSearch( - ANIMATE_DURATION, - SUGGESTIONS_BACKGROUND, - GO_TO_SUB_FIELD, - CLOSE_BUTTON); - - // Get focus of the search field and show the keyboard - GO_TO_SUB_FIELD.requestFocus(); - KeyboardUtil.toggleKeyboard( - MainActivity.this, - InputMethodManager.SHOW_FORCED, - InputMethodManager.HIDE_IMPLICIT_ONLY); - - // Close the search UI and keyboard when clicking the close - // button - CLOSE_BUTTON.setOnClickListener( - new View.OnClickListener() { - @Override - public void onClick(View v) { - final View view = - MainActivity.this.getCurrentFocus(); - if (view != null) { - // Hide the keyboard - KeyboardUtil.hideKeyboard( - MainActivity.this, - view.getWindowToken(), - 0); - } - - // run the exit animations - exitAnimationsForToolbarSearch( - ANIMATE_DURATION, - SUGGESTIONS_BACKGROUND, - GO_TO_SUB_FIELD, - CLOSE_BUTTON); - - // clear sub text when close button is - // clicked - GO_TO_SUB_FIELD.setText(""); - } - }); - - GO_TO_SUB_FIELD.setOnEditorActionListener( - new TextView.OnEditorActionListener() { - @Override - public boolean onEditorAction( - TextView arg0, - int arg1, - KeyEvent arg2) { - if (arg1 == EditorInfo.IME_ACTION_SEARCH) { - // If it the input text doesn't match a - // subreddit from the list exactly, - // openInSubView is true - if (sideArrayAdapter.fitems == null - || sideArrayAdapter - .openInSubView - || !usedArray.contains( - GO_TO_SUB_FIELD - .getText() - .toString() - .toLowerCase( - Locale - .ENGLISH))) { - Intent intent = - new Intent( - MainActivity.this, - SubredditView - .class); - intent.putExtra( - SubredditView - .EXTRA_SUBREDDIT, - GO_TO_SUB_FIELD - .getText() - .toString()); - MainActivity.this - .startActivityForResult( - intent, 2002); - } else { - if (commentPager - && adapter - instanceof - MainPagerAdapterComment) { - openingComments = null; - toOpenComments = -1; - ((MainPagerAdapterComment) - adapter) - .size = - (usedArray.size() + 1); - adapter.notifyDataSetChanged(); - - if (usedArray.contains( - GO_TO_SUB_FIELD - .getText() - .toString() - .toLowerCase( - Locale - .ENGLISH))) { - doPageSelectedComments( - usedArray.indexOf( - GO_TO_SUB_FIELD - .getText() - .toString() - .toLowerCase( - Locale - .ENGLISH))); - } else { - doPageSelectedComments( - usedArray.indexOf( - sideArrayAdapter - .fitems - .get( - 0))); - } - } - if (usedArray.contains( - GO_TO_SUB_FIELD - .getText() - .toString() - .toLowerCase( - Locale - .ENGLISH))) { - pager.setCurrentItem( - usedArray.indexOf( - GO_TO_SUB_FIELD - .getText() - .toString() - .toLowerCase( - Locale - .ENGLISH))); - } else { - pager.setCurrentItem( - usedArray.indexOf( - sideArrayAdapter - .fitems - .get( - 0))); - } - } - - View view = - MainActivity.this - .getCurrentFocus(); - if (view != null) { - // Hide the keyboard - KeyboardUtil.hideKeyboard( - MainActivity.this, - view.getWindowToken(), - 0); - } - - SUGGESTIONS_BACKGROUND.setVisibility( - View.GONE); - GO_TO_SUB_FIELD.setVisibility( - View.GONE); - CLOSE_BUTTON.setVisibility(View.GONE); - - if (SettingValues.single) { - getSupportActionBar() - .setTitle(selectedSub); - } else { - // Set the title back to "Slide" or - // "Slide (debug)" - getSupportActionBar() - .setTitle(tabViewModeTitle); - } - } - return false; - } - }); - - GO_TO_SUB_FIELD.addTextChangedListener( - new SimpleTextWatcher() { - @Override - public void afterTextChanged( - Editable editable) { - final String RESULT = - GO_TO_SUB_FIELD - .getText() - .toString() - .replaceAll(" ", ""); - TOOLBAR_SEARCH_SUGGEST_ADAPTER - .getFilter() - .filter(RESULT); - } - }); - } - return true; - } - }); - } - } - } - } } diff --git a/app/src/main/java/me/edgan/redditslide/Activities/ToolbarSearchController.java b/app/src/main/java/me/edgan/redditslide/Activities/ToolbarSearchController.java new file mode 100644 index 000000000..88fa20af9 --- /dev/null +++ b/app/src/main/java/me/edgan/redditslide/Activities/ToolbarSearchController.java @@ -0,0 +1,294 @@ +package me.edgan.redditslide.Activities; + +import android.content.Intent; +import android.os.Handler; +import android.text.Editable; +import me.edgan.redditslide.util.stubs.SimpleTextWatcher; +import android.view.KeyEvent; +import android.view.View; +import android.view.animation.AccelerateDecelerateInterpolator; +import android.view.inputmethod.EditorInfo; +import android.view.inputmethod.InputMethodManager; +import android.widget.AutoCompleteTextView; +import android.widget.ImageView; +import android.widget.ListView; +import android.widget.TextView; + +import androidx.cardview.widget.CardView; + +import java.util.ArrayList; +import java.util.Locale; + +import me.edgan.redditslide.Adapters.SideArrayAdapter; +import me.edgan.redditslide.Constants; +import me.edgan.redditslide.R; +import me.edgan.redditslide.SettingValues; +import me.edgan.redditslide.UserSubscriptions; +import me.edgan.redditslide.util.KeyboardUtil; +import me.edgan.redditslide.util.NetworkUtil; + +public class ToolbarSearchController { + + private final MainActivity mainActivity; + + public ToolbarSearchController(MainActivity mainActivity) { + this.mainActivity = mainActivity; + } + + /** + * If the user has the Subreddit Search method set to "long press on toolbar title", an + * OnLongClickListener needs to be set for the toolbar as well as handling all of the relevant + * onClicks for the views of the search bar. + */ + public void setupSubredditSearchToolbar() { + if (!NetworkUtil.isConnected(mainActivity)) { + if (mainActivity.findViewById(R.id.drawer_divider) != null) { + mainActivity.findViewById(R.id.drawer_divider).setVisibility(View.GONE); + } + } else { + if ((SettingValues.subredditSearchMethod == Constants.SUBREDDIT_SEARCH_METHOD_TOOLBAR + || SettingValues.subredditSearchMethod == Constants.SUBREDDIT_SEARCH_METHOD_BOTH) + && mainActivity.usedArray != null && !mainActivity.usedArray.isEmpty()) { + if (mainActivity.findViewById(R.id.drawer_divider) != null) { + if (SettingValues.subredditSearchMethod == Constants.SUBREDDIT_SEARCH_METHOD_BOTH) { + mainActivity.findViewById(R.id.drawer_divider).setVisibility(View.GONE); + } else { + mainActivity.findViewById(R.id.drawer_divider).setVisibility(View.VISIBLE); + } + } + + final ListView TOOLBAR_SEARCH_SUGGEST_LIST = (ListView) mainActivity.findViewById(R.id.toolbar_search_suggestions_list); + final ArrayList subs_copy = new ArrayList<>(mainActivity.usedArray); + final SideArrayAdapter TOOLBAR_SEARCH_SUGGEST_ADAPTER = + new SideArrayAdapter(mainActivity, subs_copy, UserSubscriptions.getAllSubreddits(mainActivity), TOOLBAR_SEARCH_SUGGEST_LIST); + + if (TOOLBAR_SEARCH_SUGGEST_LIST != null) { + TOOLBAR_SEARCH_SUGGEST_LIST.setAdapter(TOOLBAR_SEARCH_SUGGEST_ADAPTER); + } + + if (mainActivity.mToolbar != null) { + mainActivity.mToolbar.setOnLongClickListener( + new View.OnLongClickListener() { + @Override + public boolean onLongClick(View v) { + final AutoCompleteTextView GO_TO_SUB_FIELD = (AutoCompleteTextView) mainActivity.findViewById(R.id.toolbar_search); + final ImageView CLOSE_BUTTON = (ImageView) mainActivity.findViewById(R.id.close_search_toolbar); + final CardView SUGGESTIONS_BACKGROUND = (CardView) mainActivity.findViewById(R.id.toolbar_search_suggestions); + + // if the view mode is set to Subreddit Tabs, save the title ("Slide" or "Slide (debug)") + mainActivity.tabViewModeTitle = (!SettingValues.single) ? mainActivity.getSupportActionBar().getTitle().toString() : null; + + mainActivity.getSupportActionBar().setTitle(""); // clear title to make room for search field + + if (GO_TO_SUB_FIELD != null && CLOSE_BUTTON != null && SUGGESTIONS_BACKGROUND != null) { + GO_TO_SUB_FIELD.setVisibility(View.VISIBLE); + CLOSE_BUTTON.setVisibility(View.VISIBLE); + SUGGESTIONS_BACKGROUND.setVisibility(View.VISIBLE); + + // run enter animations + enterAnimationsForToolbarSearch(mainActivity.ANIMATE_DURATION, SUGGESTIONS_BACKGROUND, GO_TO_SUB_FIELD, CLOSE_BUTTON); + + // Get focus of the search field and show the keyboard + GO_TO_SUB_FIELD.requestFocus(); + KeyboardUtil.toggleKeyboard(mainActivity, InputMethodManager.SHOW_FORCED, InputMethodManager.HIDE_IMPLICIT_ONLY); + + // Close the search UI and keyboard when clicking the close button + CLOSE_BUTTON.setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View v) { + final View view = mainActivity.getCurrentFocus(); + if (view != null) { + // Hide the keyboard + KeyboardUtil.hideKeyboard(mainActivity, view.getWindowToken(), 0); + } + + // run the exit animations + exitAnimationsForToolbarSearch(mainActivity.ANIMATE_DURATION, SUGGESTIONS_BACKGROUND, GO_TO_SUB_FIELD, CLOSE_BUTTON); + + // clear sub text when close button is clicked + GO_TO_SUB_FIELD.setText(""); + } + }); + + GO_TO_SUB_FIELD.setOnEditorActionListener( + new TextView.OnEditorActionListener() { + @Override + public boolean onEditorAction(TextView arg0, int arg1, KeyEvent arg2) { + if (arg1 == EditorInfo.IME_ACTION_SEARCH) { + // If it the input text doesn't match a subreddit from the list exactly, openInSubView is true + if (TOOLBAR_SEARCH_SUGGEST_ADAPTER.fitems == null + || TOOLBAR_SEARCH_SUGGEST_ADAPTER.openInSubView + || !mainActivity.usedArray.contains( + GO_TO_SUB_FIELD + .getText() + .toString() + .toLowerCase(Locale.ENGLISH))) { + Intent intent = new Intent(mainActivity, SubredditView.class); + intent.putExtra(SubredditView.EXTRA_SUBREDDIT, GO_TO_SUB_FIELD.getText().toString()); + mainActivity.startActivityForResult(intent, 2002); + } else { + if (mainActivity.commentPager && mainActivity.adapter instanceof MainPagerAdapterComment) { + mainActivity.openingComments = null; + mainActivity.toOpenComments = -1; + ((MainPagerAdapterComment) mainActivity.adapter).size = (mainActivity.usedArray.size() + 1); + mainActivity.adapter.notifyDataSetChanged(); + + if (mainActivity.usedArray.contains(GO_TO_SUB_FIELD.getText().toString().toLowerCase(Locale.ENGLISH))) { + mainActivity.doPageSelectedComments( + mainActivity.usedArray.indexOf(GO_TO_SUB_FIELD.getText().toString().toLowerCase(Locale.ENGLISH)) + ); + } else { + mainActivity.doPageSelectedComments( + mainActivity.usedArray.indexOf(TOOLBAR_SEARCH_SUGGEST_ADAPTER.fitems.get(0)) + ); + } + } + if (mainActivity.usedArray.contains(GO_TO_SUB_FIELD.getText().toString().toLowerCase(Locale.ENGLISH))) { + mainActivity.pager.setCurrentItem( + mainActivity.usedArray.indexOf( + GO_TO_SUB_FIELD + .getText() + .toString() + .toLowerCase(Locale.ENGLISH) + ) + ); + } else { + mainActivity.pager.setCurrentItem(mainActivity.usedArray.indexOf(TOOLBAR_SEARCH_SUGGEST_ADAPTER.fitems.get(0))); + } + } + + View view = mainActivity.getCurrentFocus(); + + if (view != null) { + // Hide the keyboard + KeyboardUtil.hideKeyboard(mainActivity, view.getWindowToken(), 0); + } + + SUGGESTIONS_BACKGROUND.setVisibility(View.GONE); + GO_TO_SUB_FIELD.setVisibility(View.GONE); + CLOSE_BUTTON.setVisibility(View.GONE); + + if (SettingValues.single) { + mainActivity.getSupportActionBar().setTitle(mainActivity.selectedSub); + } else { + // Set the title back to "Slide" or "Slide (debug)" + mainActivity.getSupportActionBar().setTitle(mainActivity.tabViewModeTitle); + } + } + + return false; + } + }); + + GO_TO_SUB_FIELD.addTextChangedListener( + new SimpleTextWatcher() { + @Override + public void afterTextChanged(Editable editable) { + final String RESULT = GO_TO_SUB_FIELD.getText().toString().replaceAll(" ", ""); + TOOLBAR_SEARCH_SUGGEST_ADAPTER.getFilter().filter(RESULT); + } + }); + } + + return true; + } + }); + } + } + } + } + + + /** + * Starts the enter animations for various UI components of the toolbar subreddit search + * + * @param ANIMATION_DURATION duration of the animation in ms + * @param SUGGESTIONS_BACKGROUND background of subreddit suggestions list + * @param GO_TO_SUB_FIELD search field in toolbar + * @param CLOSE_BUTTON button that clears the search and closes the search UI + */ + public void enterAnimationsForToolbarSearch( + final long ANIMATION_DURATION, + final CardView SUGGESTIONS_BACKGROUND, + final AutoCompleteTextView GO_TO_SUB_FIELD, + final ImageView CLOSE_BUTTON) { + SUGGESTIONS_BACKGROUND + .animate() + .translationY(mainActivity.headerHeight) + .setInterpolator(new AccelerateDecelerateInterpolator()) + .setDuration(ANIMATION_DURATION + mainActivity.ANIMATE_DURATION_OFFSET) + .start(); + + GO_TO_SUB_FIELD + .animate() + .alpha(1f) + .setInterpolator(new AccelerateDecelerateInterpolator()) + .setDuration(ANIMATION_DURATION) + .start(); + + CLOSE_BUTTON + .animate() + .alpha(1f) + .setInterpolator(new AccelerateDecelerateInterpolator()) + .setDuration(ANIMATION_DURATION) + .start(); + } + + /** + * Starts the exit animations for various UI components of the toolbar subreddit search + * + * @param ANIMATION_DURATION duration of the animation in ms + * @param SUGGESTIONS_BACKGROUND background of subreddit suggestions list + * @param GO_TO_SUB_FIELD search field in toolbar + * @param CLOSE_BUTTON button that clears the search and closes the search UI + */ + public void exitAnimationsForToolbarSearch( + final long ANIMATION_DURATION, + final CardView SUGGESTIONS_BACKGROUND, + final AutoCompleteTextView GO_TO_SUB_FIELD, + final ImageView CLOSE_BUTTON) { + SUGGESTIONS_BACKGROUND + .animate() + .translationY(-SUGGESTIONS_BACKGROUND.getHeight()) + .setInterpolator(new AccelerateDecelerateInterpolator()) + .setDuration(ANIMATION_DURATION + mainActivity.ANIMATE_DURATION_OFFSET) + .start(); + + GO_TO_SUB_FIELD + .animate() + .alpha(0f) + .setInterpolator(new AccelerateDecelerateInterpolator()) + .setDuration(ANIMATION_DURATION) + .start(); + + CLOSE_BUTTON + .animate() + .alpha(0f) + .setInterpolator(new AccelerateDecelerateInterpolator()) + .setDuration(ANIMATION_DURATION) + .start(); + + // Helps smooth the transition between the toolbar title being reset and the search elements + // fading out. + final long OFFSET_ANIM = (ANIMATION_DURATION == 0) ? 0 : mainActivity.ANIMATE_DURATION_OFFSET; + + // Hide the various UI components after the animations are complete and reset the toolbar title + new Handler().postDelayed( + new Runnable() { + @Override + public void run() { + SUGGESTIONS_BACKGROUND.setVisibility(View.GONE); + GO_TO_SUB_FIELD.setVisibility(View.GONE); + CLOSE_BUTTON.setVisibility(View.GONE); + + if (SettingValues.single) { + mainActivity.getSupportActionBar().setTitle(mainActivity.selectedSub); + } else { + mainActivity.getSupportActionBar().setTitle(mainActivity.tabViewModeTitle); + } + } + }, + ANIMATION_DURATION + mainActivity.ANIMATE_DURATION_OFFSET); + } +} \ No newline at end of file diff --git a/app/src/main/java/me/edgan/redditslide/Adapters/SideArrayAdapter.java b/app/src/main/java/me/edgan/redditslide/Adapters/SideArrayAdapter.java index fa083dc59..8599c64ce 100644 --- a/app/src/main/java/me/edgan/redditslide/Adapters/SideArrayAdapter.java +++ b/app/src/main/java/me/edgan/redditslide/Adapters/SideArrayAdapter.java @@ -106,7 +106,7 @@ private void hideSearchbarUI() { // to animate upon the next time // the search toolbar UI is called. Set animation to 0 because the UI is already // hidden. - ((MainActivity) getContext()) + ((MainActivity) getContext()).toolbarSearchController .exitAnimationsForToolbarSearch( 0, ((CardView) From df8ee622778cdbf1ea8e9fc0c484051d8ff3099b Mon Sep 17 00:00:00 2001 From: Nathan Grennan Date: Sun, 30 Mar 2025 23:56:43 -0700 Subject: [PATCH 07/99] Broke out SidebarController class from MainActivity.java --- .../Activities/AsyncGetSubredditTask.java | 2 +- .../Activities/DrawerController.java | 6 +- .../redditslide/Activities/MainActivity.java | 1288 +---------------- .../Activities/MainPagerAdapter.java | 2 +- .../Activities/SidebarController.java | 1046 +++++++++++++ 5 files changed, 1061 insertions(+), 1283 deletions(-) create mode 100644 app/src/main/java/me/edgan/redditslide/Activities/SidebarController.java diff --git a/app/src/main/java/me/edgan/redditslide/Activities/AsyncGetSubredditTask.java b/app/src/main/java/me/edgan/redditslide/Activities/AsyncGetSubredditTask.java index 89dfe8d00..308dc7843 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/AsyncGetSubredditTask.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/AsyncGetSubredditTask.java @@ -22,7 +22,7 @@ public AsyncGetSubredditTask(MainActivity activity) { public void onPostExecute(Subreddit subreddit) { // Ensure mainActivity is still valid before calling its method if (mainActivity != null && !mainActivity.isDestroyed() && subreddit != null) { - mainActivity.doSubOnlyStuff(subreddit); + mainActivity.sidebarController.doSubOnlyStuff(subreddit); } else if (mainActivity != null && !mainActivity.isDestroyed()) { // Handle cases where subreddit is null (e.g., network error, subreddit not found) Log.w(LogUtil.getTag(), "Failed to fetch subreddit details or subreddit is null."); diff --git a/app/src/main/java/me/edgan/redditslide/Activities/DrawerController.java b/app/src/main/java/me/edgan/redditslide/Activities/DrawerController.java index bf1e29689..2589fa970 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/DrawerController.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/DrawerController.java @@ -839,11 +839,11 @@ public void onDrawerOpened(View drawerView) { .adapter .dataSet .subredditRandom; - mainActivity.doSubSidebarNoLoad(sub); - mainActivity.doSubSidebar(sub); + mainActivity.sidebarController.doSubSidebarNoLoad(sub); + mainActivity.sidebarController.doSubSidebar(sub); } } else { - mainActivity.doSubSidebar(mainActivity.usedArray.get(current)); + mainActivity.sidebarController.doSubSidebar(mainActivity.usedArray.get(current)); } } } diff --git a/app/src/main/java/me/edgan/redditslide/Activities/MainActivity.java b/app/src/main/java/me/edgan/redditslide/Activities/MainActivity.java index 4f8fd9f81..c9b89f860 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/MainActivity.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/MainActivity.java @@ -40,11 +40,8 @@ import android.view.Window; import android.view.animation.LinearInterpolator; import android.widget.AutoCompleteTextView; -import android.widget.CompoundButton; import android.widget.EditText; import android.widget.FrameLayout; -import android.widget.HorizontalScrollView; -import android.widget.ImageView; import android.widget.ListView; import android.widget.TextView; import android.widget.Toast; @@ -53,7 +50,6 @@ import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.view.ContextThemeWrapper; -import androidx.appcompat.widget.AppCompatCheckBox; import androidx.appcompat.widget.PopupMenu; import androidx.appcompat.widget.SwitchCompat; import androidx.appcompat.widget.Toolbar; @@ -68,7 +64,6 @@ import com.afollestad.materialdialogs.DialogAction; import com.afollestad.materialdialogs.MaterialDialog; -import com.fasterxml.jackson.databind.JsonNode; import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.snackbar.Snackbar; import com.google.android.material.tabs.TabLayout; @@ -85,7 +80,6 @@ import me.edgan.redditslide.Constants; import me.edgan.redditslide.ForceTouch.util.DensityUtils; import me.edgan.redditslide.Fragments.SubmissionsView; -import me.edgan.redditslide.ImageFlairs; import me.edgan.redditslide.Notifications.CheckForMail; import me.edgan.redditslide.R; import me.edgan.redditslide.Reddit; @@ -97,7 +91,6 @@ import me.edgan.redditslide.Views.CatchStaggeredGridLayoutManager; import me.edgan.redditslide.Views.CommentOverflow; import me.edgan.redditslide.Views.PreCachingLayoutManager; -import me.edgan.redditslide.Views.SidebarLayout; import me.edgan.redditslide.Views.ToggleSwipeViewPager; import me.edgan.redditslide.Visuals.ColorPreferences; import me.edgan.redditslide.Visuals.Palette; @@ -110,32 +103,20 @@ import me.edgan.redditslide.util.KeyboardUtil; import me.edgan.redditslide.util.LayoutUtils; import me.edgan.redditslide.util.LogUtil; -import me.edgan.redditslide.util.MiscUtil; import me.edgan.redditslide.util.NetworkStateReceiver; import me.edgan.redditslide.util.NetworkUtil; import me.edgan.redditslide.util.OnSingleClickListener; import me.edgan.redditslide.util.SortingUtil; -import me.edgan.redditslide.util.StringUtil; -import me.edgan.redditslide.util.SubmissionParser; import me.edgan.redditslide.util.TimeUtils; import me.edgan.redditslide.util.FilterContentUtil; -import net.dean.jraw.ApiException; -import net.dean.jraw.http.MultiRedditUpdateRequest; -import net.dean.jraw.http.NetworkException; import net.dean.jraw.managers.AccountManager; -import net.dean.jraw.managers.ModerationManager; -import net.dean.jraw.managers.MultiRedditManager; -import net.dean.jraw.models.FlairTemplate; import net.dean.jraw.models.MultiReddit; -import net.dean.jraw.models.MultiSubreddit; import net.dean.jraw.models.Submission; import net.dean.jraw.models.Subreddit; -import net.dean.jraw.models.UserRecord; import net.dean.jraw.paginators.Sorting; import net.dean.jraw.paginators.SubredditPaginator; import net.dean.jraw.paginators.TimePeriod; -import net.dean.jraw.paginators.UserRecordPaginator; import java.lang.reflect.Field; import java.util.ArrayList; @@ -198,16 +179,16 @@ public class MainActivity extends BaseActivity String term; View headerMain; MaterialDialog d; - AsyncTask currentFlair; - SpoilerRobotoTextView sidebarBody; - CommentOverflow sidebarOverflow; + public AsyncTask currentFlair; // Made public + public SpoilerRobotoTextView sidebarBody; // Made public + public CommentOverflow sidebarOverflow; // Made public View accountsArea; SideArrayAdapter sideArrayAdapter; Menu menu; AsyncTask caching; - boolean currentlySubbed; + public boolean currentlySubbed; // Made public int back; - AsyncGetSubredditTask mAsyncGetSubreddit = null; + public AsyncGetSubredditTask mAsyncGetSubreddit = null; // Made public int headerHeight; public int reloadItemNumber = -2; private static final int NOTIFICATION_PERMISSION_REQUEST_CODE = 1001; @@ -216,6 +197,7 @@ public class MainActivity extends BaseActivity DrawerController drawerController; public ToolbarSearchController toolbarSearchController; + SidebarController sidebarController; // Added declaration @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == SETTINGS_RESULT) { @@ -957,6 +939,7 @@ public void onSingleClick( setContentView(R.layout.activity_overview); drawerController = new DrawerController(this); toolbarSearchController = new ToolbarSearchController(this); + sidebarController = new SidebarController(this); // Added initialization rootView = findViewById(android.R.id.content); @@ -1346,7 +1329,7 @@ public void doPageSelectedComments(int position) { Reddit.currentPosition = position; if (position + 1 != currentComment) { - doSubSidebarNoLoad(usedArray.get(position)); + sidebarController.doSubSidebarNoLoad(usedArray.get(position)); } SubmissionsView page = (SubmissionsView) adapter.getCurrentFragment(); if (page != null && page.adapter != null) { @@ -1385,1174 +1368,9 @@ public void doPageSelectedComments(int position) { selectedSub = usedArray.get(position); } - public void doSubOnlyStuff(final Subreddit subreddit) { - findViewById(R.id.loader).setVisibility(View.GONE); - if (subreddit.getSubredditType() != null) { - canSubmit = !subreddit.getSubredditType().equals("RESTRICTED"); - } else { - canSubmit = true; - } - if (subreddit.getSidebar() != null && !subreddit.getSidebar().isEmpty()) { - findViewById(R.id.sidebar_text).setVisibility(View.VISIBLE); - - final String text = subreddit.getDataNode().get("description_html").asText().trim(); - setViews(text, subreddit.getDisplayName(), sidebarBody, sidebarOverflow); - - // get all subs that have Notifications enabled - ArrayList rawSubs = - StringUtil.stringToArray( - Reddit.appRestart.getString(CheckForMail.SUBS_TO_GET, "")); - HashMap subThresholds = new HashMap<>(); - for (String s : rawSubs) { - try { - String[] split = s.split(":"); - subThresholds.put( - split[0].toLowerCase(Locale.ENGLISH), Integer.valueOf(split[1])); - } catch (Exception ignored) { - // do nothing - } - } - - // whether or not this subreddit was in the keySet - boolean isNotified = - subThresholds.containsKey( - subreddit.getDisplayName().toLowerCase(Locale.ENGLISH)); - ((AppCompatCheckBox) findViewById(R.id.notify_posts_state)).setChecked(isNotified); - } else { - findViewById(R.id.sidebar_text).setVisibility(View.GONE); - } - { - View collection = findViewById(R.id.collection); - if (Authentication.isLoggedIn) { - collection.setOnClickListener( - new View.OnClickListener() { - @Override - public void onClick(View v) { - new AsyncTask() { - HashMap multis = - new HashMap(); - - @Override - protected Void doInBackground(Void... params) { - if (UserSubscriptions.multireddits == null) { - UserSubscriptions.syncMultiReddits(MainActivity.this); - } - for (MultiReddit r : UserSubscriptions.multireddits) { - multis.put(r.getDisplayName(), r); - } - return null; - } - - @Override - protected void onPostExecute(Void aVoid) { - new MaterialDialog.Builder(MainActivity.this) - .title( - getString( - R.string.multi_add_to, - subreddit.getDisplayName())) - .items(multis.keySet()) - .itemsCallback( - new MaterialDialog.ListCallback() { - @Override - public void onSelection( - MaterialDialog dialog, - View itemView, - final int which, - CharSequence text) { - new AsyncTask() { - @Override - protected Void doInBackground( - Void... params) { - try { - final String multiName = - multis.keySet() - .toArray( - new String - [0])[ - which]; - List subs = - new ArrayList< - String>(); - for (MultiSubreddit - sub : - multis.get( - multiName) - .getSubreddits()) { - subs.add( - sub - .getDisplayName()); - } - subs.add( - subreddit - .getDisplayName()); - new MultiRedditManager( - Authentication - .reddit) - .createOrUpdate( - new MultiRedditUpdateRequest - .Builder( - Authentication - .name, - multiName) - .subreddits( - subs) - .build()); - - UserSubscriptions - .syncMultiReddits( - MainActivity - .this); - - runOnUiThread( - new Runnable() { - @Override - public void - run() { - drawerLayout - .closeDrawers(); - Snackbar - s = - Snackbar - .make( - mToolbar, - getString( - R - .string - .multi_subreddit_added, - multiName), - Snackbar - .LENGTH_LONG); - LayoutUtils - .showSnackbar( - s); - } - }); - } catch (final - NetworkException - | ApiException e) { - runOnUiThread( - new Runnable() { - @Override - public void - run() { - runOnUiThread( - new Runnable() { - @Override - public - void - run() { - Snackbar - .make( - mToolbar, - getString( - R - .string - .multi_error), - Snackbar - .LENGTH_LONG) - .setAction( - R - .string - .btn_ok, - null) - .show(); - } - }); - } - }); - e.printStackTrace(); - } - return null; - } - }.executeOnExecutor( - AsyncTask - .THREAD_POOL_EXECUTOR); - } - }) - .show(); - } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } - }); - } else { - collection.setVisibility(View.GONE); - } - } - { - final AppCompatCheckBox notifyStateCheckBox = - (AppCompatCheckBox) findViewById(R.id.notify_posts_state); - assert notifyStateCheckBox != null; - - notifyStateCheckBox.setOnCheckedChangeListener( - new CompoundButton.OnCheckedChangeListener() { - @Override - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - if (isChecked) { - final String sub = subreddit.getDisplayName(); - - if (!sub.equalsIgnoreCase("all") - && !sub.equalsIgnoreCase("frontpage") - && !sub.equalsIgnoreCase("friends") - && !sub.equalsIgnoreCase("mod") - && !sub.contains("+") - && !sub.contains(".") - && !sub.contains("/m/")) { - new AlertDialog.Builder(MainActivity.this) - .setTitle( - getString(R.string.sub_post_notifs_title, sub)) - .setMessage(R.string.sub_post_notifs_msg) - .setPositiveButton( - R.string.btn_ok, - (dialog, which) -> - new MaterialDialog.Builder( - MainActivity.this) - .title( - R.string - .sub_post_notifs_threshold) - .items( - new String[] { - "1", "5", "10", - "20", "40", "50" - }) - .alwaysCallSingleChoiceCallback() - .itemsCallbackSingleChoice( - 0, - new MaterialDialog - .ListCallbackSingleChoice() { - @Override - public boolean - onSelection( - MaterialDialog - dialog, - View - itemView, - int - which, - CharSequence - text) { - ArrayList< - String> - subs = - StringUtil - .stringToArray( - Reddit - .appRestart - .getString( - CheckForMail - .SUBS_TO_GET, - "")); - subs.add( - sub - + ":" - + text); - Reddit - .appRestart - .edit() - .putString( - CheckForMail - .SUBS_TO_GET, - StringUtil - .arrayToString( - subs)) - .commit(); - return true; - } - }) - .cancelable(false) - .show()) - .setNegativeButton(R.string.btn_cancel, null) - .setNegativeButton( - R.string.btn_cancel, - (dialog, which) -> - notifyStateCheckBox.setChecked(false)) - .setOnCancelListener( - dialog -> notifyStateCheckBox.setChecked(false)) - .show(); - } else { - notifyStateCheckBox.setChecked(false); - Toast.makeText( - MainActivity.this, - R.string.sub_post_notifs_err, - Toast.LENGTH_SHORT) - .show(); - } - } else { - Intent cancelIntent = - new Intent(MainActivity.this, CancelSubNotifs.class); - cancelIntent.putExtra( - CancelSubNotifs.EXTRA_SUB, subreddit.getDisplayName()); - startActivity(cancelIntent); - } - } - }); - } - { - final TextView subscribe = (TextView) findViewById(R.id.subscribe); - currentlySubbed = - (!Authentication.isLoggedIn - && usedArray.contains( - subreddit.getDisplayName().toLowerCase(Locale.ENGLISH))) - || subreddit.isUserSubscriber(); - MiscUtil.doSubscribeButtonText(currentlySubbed, subscribe); - - assert subscribe != null; - subscribe.setOnClickListener( - new View.OnClickListener() { - private void doSubscribe() { - if (Authentication.isLoggedIn) { - new AlertDialog.Builder(MainActivity.this) - .setTitle( - getString( - R.string.subscribe_to, - subreddit.getDisplayName())) - .setPositiveButton( - R.string.reorder_add_subscribe, - (dialog, which) -> - new AsyncTask() { - @Override - public void onPostExecute( - Boolean success) { - if (!success) { // If subreddit was - // removed from - // account or not - new AlertDialog.Builder( - MainActivity - .this) - .setTitle( - R.string - .force_change_subscription) - .setMessage( - R.string - .force_change_subscription_desc) - .setPositiveButton( - R.string - .btn_yes, - (dialog1, - which1) -> { - changeSubscription( - subreddit, - true); // Force add the subscription - Snackbar s = - Snackbar - .make( - mToolbar, - getString( - R - .string - .misc_subscribed), - Snackbar - .LENGTH_LONG); - LayoutUtils - .showSnackbar( - s); - }) - .setNegativeButton( - R.string.btn_no, - null) - .setCancelable(false) - .show(); - } else { - changeSubscription( - subreddit, true); - } - } - - @Override - protected Boolean doInBackground( - Void... params) { - try { - new AccountManager( - Authentication - .reddit) - .subscribe(subreddit); - } catch (NetworkException e) { - return false; // Either network crashed or trying to unsubscribe to a - // subreddit that the account isn't subscribed to - } - return true; - } - }.executeOnExecutor( - AsyncTask.THREAD_POOL_EXECUTOR)) - .setNeutralButton( - R.string.btn_add_to_sublist, - (dialog, which) -> { - changeSubscription( - subreddit, - true); // Force add the subscription - Snackbar s = - Snackbar.make( - mToolbar, - R.string.sub_added, - Snackbar.LENGTH_LONG); - LayoutUtils.showSnackbar(s); - }) - .setNegativeButton(R.string.btn_cancel, null) - .show(); - } else { - changeSubscription(subreddit, true); - } - } - - private void doUnsubscribe() { - if (Authentication.didOnline) { - new AlertDialog.Builder(MainActivity.this) - .setTitle( - getString( - R.string.unsubscribe_from, - subreddit.getDisplayName())) - .setPositiveButton( - R.string.reorder_remove_unsubscribe, - (dialog, which) -> - new AsyncTask() { - @Override - public void onPostExecute( - Boolean success) { - if (!success) { // If subreddit was - // removed from - // account or not - new AlertDialog.Builder( - MainActivity - .this) - .setTitle( - R.string - .force_change_subscription) - .setMessage( - R.string - .force_change_subscription_desc) - .setPositiveButton( - R.string - .btn_yes, - (dialog12, - which12) -> { - changeSubscription( - subreddit, - false); // Force add the subscription - Snackbar s = - Snackbar - .make( - mToolbar, - getString( - R - .string - .misc_unsubscribed), - Snackbar - .LENGTH_LONG); - LayoutUtils - .showSnackbar( - s); - }) - .setNegativeButton( - R.string.btn_no, - null) - .setCancelable(false) - .show(); - } else { - changeSubscription( - subreddit, false); - } - } - - @Override - protected Boolean doInBackground( - Void... params) { - try { - new AccountManager( - Authentication - .reddit) - .unsubscribe(subreddit); - } catch (NetworkException e) { - return false; // Either network crashed or trying to unsubscribe to a - // subreddit that the account isn't subscribed to - } - return true; - } - }.executeOnExecutor( - AsyncTask.THREAD_POOL_EXECUTOR)) - .setNeutralButton( - R.string.just_unsub, - (dialog, which) -> { - changeSubscription( - subreddit, - false); // Force add the subscription - Snackbar s = - Snackbar.make( - mToolbar, - R.string.misc_unsubscribed, - Snackbar.LENGTH_LONG); - LayoutUtils.showSnackbar(s); - }) - .setNegativeButton(R.string.btn_cancel, null) - .show(); - } else { - changeSubscription(subreddit, false); - } - } - - @Override - public void onClick(View v) { - if (!currentlySubbed) { - doSubscribe(); - } else { - doUnsubscribe(); - } - MiscUtil.doSubscribeButtonText(currentlySubbed, subscribe); - } - }); - } - if (!subreddit.getPublicDescription().isEmpty()) { - findViewById(R.id.sub_title).setVisibility(View.VISIBLE); - setViews( - subreddit.getDataNode().get("public_description_html").asText(), - subreddit.getDisplayName().toLowerCase(Locale.ENGLISH), - ((SpoilerRobotoTextView) findViewById(R.id.sub_title)), - (CommentOverflow) findViewById(R.id.sub_title_overflow)); - } else { - findViewById(R.id.sub_title).setVisibility(View.GONE); - } - ((ImageView) findViewById(R.id.subimage)).setImageResource(0); - if (subreddit.getDataNode().has("icon_img") - && !subreddit.getDataNode().get("icon_img").asText().isEmpty()) { - findViewById(R.id.subimage).setVisibility(View.VISIBLE); - ((Reddit) getApplication()) - .getImageLoader() - .displayImage( - subreddit.getDataNode().get("icon_img").asText(), - (ImageView) findViewById(R.id.subimage)); - } else { - findViewById(R.id.subimage).setVisibility(View.GONE); - } - String bannerImage = subreddit.getBannerImage(); - if (bannerImage != null && !bannerImage.isEmpty()) { - findViewById(R.id.sub_banner).setVisibility(View.VISIBLE); - ((Reddit) getApplication()) - .getImageLoader() - .displayImage(bannerImage, (ImageView) findViewById(R.id.sub_banner)); - } else { - findViewById(R.id.sub_banner).setVisibility(View.GONE); - } - ((TextView) findViewById(R.id.subscribers)) - .setText( - getString( - R.string.subreddit_subscribers_string, - subreddit.getLocalizedSubscriberCount())); - findViewById(R.id.subscribers).setVisibility(View.VISIBLE); - - ((TextView) findViewById(R.id.active_users)) - .setText( - getString( - R.string.subreddit_active_users_string_new, - subreddit.getLocalizedAccountsActive())); - findViewById(R.id.active_users).setVisibility(View.VISIBLE); - } Sorting sorts; - public void doSubSidebar(final String subreddit) { - if (mAsyncGetSubreddit != null) { - mAsyncGetSubreddit.cancel(true); - } - findViewById(R.id.loader).setVisibility(View.VISIBLE); - - invalidateOptionsMenu(); - - if (!subreddit.equalsIgnoreCase("all") - && !subreddit.equalsIgnoreCase("frontpage") - && !subreddit.equalsIgnoreCase("friends") - && !subreddit.equalsIgnoreCase("mod") - && !subreddit.contains("+") - && !subreddit.contains(".") - && !subreddit.contains("/m/")) { - if (drawerLayout != null) { - drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, GravityCompat.END); - } - - mAsyncGetSubreddit = new AsyncGetSubredditTask(this); - mAsyncGetSubreddit.execute(subreddit); - - final View dialoglayout = findViewById(R.id.sidebarsub); - { - View submit = (dialoglayout.findViewById(R.id.submit)); - - if (!Authentication.isLoggedIn || !Authentication.didOnline) { - submit.setVisibility(View.GONE); - } - if (SettingValues.fab && SettingValues.fabType == Constants.FAB_POST) { - submit.setVisibility(View.GONE); - } - - submit.setOnClickListener( - new OnSingleClickListener() { - @Override - public void onSingleClick(View view) { - Intent inte = new Intent(MainActivity.this, Submit.class); - if (!subreddit.contains("/m/") && canSubmit) { - inte.putExtra(Submit.EXTRA_SUBREDDIT, subreddit); - } - MainActivity.this.startActivity(inte); - } - }); - } - - dialoglayout - .findViewById(R.id.wiki) - .setOnClickListener( - new View.OnClickListener() { - @Override - public void onClick(View v) { - Intent i = new Intent(MainActivity.this, Wiki.class); - i.putExtra(Wiki.EXTRA_SUBREDDIT, subreddit); - startActivity(i); - } - }); - dialoglayout - .findViewById(R.id.syncflair) - .setOnClickListener( - new View.OnClickListener() { - @Override - public void onClick(View v) { - ImageFlairs.syncFlairs(MainActivity.this, subreddit); - } - }); - dialoglayout - .findViewById(R.id.submit) - .setOnClickListener( - new View.OnClickListener() { - @Override - public void onClick(View v) { - Intent i = new Intent(MainActivity.this, Submit.class); - if ((!subreddit.contains("/m/") || !subreddit.contains(".")) - && canSubmit) { - i.putExtra(Submit.EXTRA_SUBREDDIT, subreddit); - } - startActivity(i); - } - }); - - final TextView sort = dialoglayout.findViewById(R.id.sort); - Sorting sortingis = Sorting.HOT; - if (SettingValues.hasSort(subreddit)) { - sortingis = SettingValues.getBaseSubmissionSort(subreddit); - sort.setText( - sortingis.name() - + ((sortingis == Sorting.CONTROVERSIAL || sortingis == Sorting.TOP) - ? " of " + SettingValues.getBaseTimePeriod(subreddit).name() - : "")); - } else { - sort.setText("Set default sorting"); - } - final int sortid = SortingUtil.getSortingId(sortingis); - dialoglayout - .findViewById(R.id.sorting) - .setOnClickListener( - new View.OnClickListener() { - @Override - public void onClick(View v) { - - final DialogInterface.OnClickListener l2 = - new DialogInterface.OnClickListener() { - @Override - public void onClick( - DialogInterface dialogInterface, int i) { - switch (i) { - case 0: - sorts = Sorting.HOT; - break; - case 1: - sorts = Sorting.NEW; - break; - case 2: - sorts = Sorting.RISING; - break; - case 3: - sorts = Sorting.TOP; - askTimePeriod( - sorts, subreddit, dialoglayout); - return; - case 4: - sorts = Sorting.CONTROVERSIAL; - askTimePeriod( - sorts, subreddit, dialoglayout); - return; - } - - SettingValues.setSubSorting( - sorts, time, subreddit); - Sorting sortingis = - SettingValues.getBaseSubmissionSort( - subreddit); - sort.setText( - sortingis.name() - + ((sortingis - == Sorting - .CONTROVERSIAL - || sortingis - == Sorting - .TOP) - ? " of " - + SettingValues - .getBaseTimePeriod( - subreddit) - .name() - : "")); - reloadSubs(); - } - }; - - new AlertDialog.Builder(MainActivity.this) - .setTitle(R.string.sorting_choose) - .setSingleChoiceItems( - SortingUtil.getSortingStrings(), sortid, l2) - .setNegativeButton( - "Reset default sorting", - (dialog, which) -> { - SettingValues.prefs - .edit() - .remove("defaultSort" + subreddit.toLowerCase(Locale.ENGLISH)) - .apply(); - SettingValues.prefs - .edit() - .remove("defaultTime" + subreddit.toLowerCase(Locale.ENGLISH)) - .apply(); - final TextView sort1 = dialoglayout.findViewById(R.id.sort); - if (SettingValues.hasSort(subreddit)) { - Sorting sortingis1 = SettingValues.getBaseSubmissionSort(subreddit); - sort1.setText(sortingis1.name() - + ((sortingis1 == Sorting.CONTROVERSIAL || sortingis1 == Sorting.TOP) - ? " of " + SettingValues.getBaseTimePeriod(subreddit).name() : "")); - } else { - sort1.setText("Set default sorting"); - } - reloadSubs(); - }) - .show(); - } - }); - - dialoglayout - .findViewById(R.id.theme) - .setOnClickListener( - new View.OnClickListener() { - @Override - public void onClick(View v) { - int style = new ColorPreferences(MainActivity.this).getThemeSubreddit(subreddit); - - final Context contextThemeWrapper = new ContextThemeWrapper(MainActivity.this, style); - LayoutInflater localInflater = getLayoutInflater().cloneInContext(contextThemeWrapper); - - final View dialoglayout = localInflater.inflate(R.layout.colorsub, null); - - ArrayList arrayList = new ArrayList<>(); - arrayList.add(subreddit); - SettingsSubAdapter.showSubThemeEditor(arrayList, MainActivity.this, dialoglayout); - } - }); - dialoglayout - .findViewById(R.id.mods) - .setOnClickListener( - new View.OnClickListener() { - @Override - public void onClick(View v) { - final Dialog d = - new MaterialDialog.Builder(MainActivity.this) - .title(R.string.sidebar_findingmods) - .cancelable(true) - .content(R.string.misc_please_wait) - .progress(true, 100) - .show(); - new AsyncTask() { - ArrayList mods; - - @Override - protected Void doInBackground(Void... params) { - mods = new ArrayList<>(); - UserRecordPaginator paginator = - new UserRecordPaginator( - Authentication.reddit, - subreddit, - "moderators"); - paginator.setSorting(Sorting.HOT); - paginator.setTimePeriod(TimePeriod.ALL); - while (paginator.hasNext()) { - mods.addAll(paginator.next()); - } - return null; - } - - @Override - protected void onPostExecute(Void aVoid) { - final ArrayList names = new ArrayList<>(); - for (UserRecord rec : mods) { - names.add(rec.getFullName()); - } - d.dismiss(); - new MaterialDialog.Builder(MainActivity.this) - .title(getString(R.string.sidebar_submods, subreddit)) - .items(names) - .itemsCallback( - new MaterialDialog.ListCallback() { - @Override - public void onSelection(MaterialDialog dialog, View itemView, int which, CharSequence text) { - Intent i = new Intent(MainActivity.this, Profile.class); - i.putExtra(Profile.EXTRA_PROFILE, names.get(which)); - startActivity(i); - } - }) - .positiveText(R.string.btn_message) - .onPositive( - new MaterialDialog - .SingleButtonCallback() { - @Override - public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { - Intent i = new Intent(MainActivity.this, SendMessage.class); - i.putExtra(SendMessage.EXTRA_NAME, "/r/" + subreddit); - startActivity(i); - } - }) - .show(); - } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } - }); - dialoglayout.findViewById(R.id.flair).setVisibility(View.GONE); - if (Authentication.didOnline && Authentication.isLoggedIn) { - if (currentFlair != null) currentFlair.cancel(true); - currentFlair = - new AsyncTask() { - List flairs; - ArrayList flairText; - String current; - AccountManager m; - - @Override - protected View doInBackground(View... params) { - try { - m = new AccountManager(Authentication.reddit); - JsonNode node = m.getFlairChoicesRootNode(subreddit, null); - flairs = m.getFlairChoices(subreddit, node); - - FlairTemplate currentF = m.getCurrentFlair(subreddit, node); - if (currentF != null) { - if (currentF.getText().isEmpty()) { - current = ("[" + currentF.getCssClass() + "]"); - } else { - current = (currentF.getText()); - } - } - flairText = new ArrayList<>(); - for (FlairTemplate temp : flairs) { - if (temp.getText().isEmpty()) { - flairText.add("[" + temp.getCssClass() + "]"); - } else { - flairText.add(temp.getText()); - } - } - } catch (Exception e1) { - e1.printStackTrace(); - } - return params[0]; - } - - @Override - protected void onPostExecute(View flair) { - if (flairs != null - && !flairs.isEmpty() - && flairText != null - && !flairText.isEmpty()) { - flair.setVisibility(View.VISIBLE); - if (current != null) { - ((TextView) dialoglayout.findViewById(R.id.flair_text)) - .setText( - getString(R.string.sidebar_flair, current)); - } - flair.setOnClickListener( - new View.OnClickListener() { - @Override - public void onClick(View v) { - new MaterialDialog.Builder(MainActivity.this) - .items(flairText) - .title(R.string.sidebar_select_flair) - .itemsCallback( - new MaterialDialog - .ListCallback() { - @Override - public void onSelection( - MaterialDialog - dialog, - View itemView, - int which, - CharSequence text) { - final FlairTemplate t = - flairs.get( - which); - if (t - .isTextEditable()) { - new MaterialDialog - .Builder( - MainActivity - .this) - .title( - R - .string - .sidebar_select_flair_text) - .input( - getString( - R - .string - .mod_flair_hint), - t - .getText(), - true, - (dialog1, - input) -> {}) - .positiveText( - R - .string - .btn_set) - .onPositive( - new MaterialDialog - .SingleButtonCallback() { - @Override - public - void - onClick( - MaterialDialog - dialog, - DialogAction - which) { - final - String - flair = - dialog.getInputEditText() - .getText() - .toString(); - new AsyncTask< - Void, - Void, - Boolean>() { - @Override - protected - Boolean - doInBackground( - Void - ... - params) { - try { - new ModerationManager( - Authentication - .reddit) - .setFlair( - subreddit, - t, - flair, - Authentication - .name); - FlairTemplate - currentF = - m - .getCurrentFlair( - subreddit); - if (currentF.getText() - .isEmpty()) { - current = - ("[" - + currentF - .getCssClass() - + "]"); - } else { - current = - (currentF - .getText()); - } - return true; - } catch ( - Exception - e) { - e - .printStackTrace(); - return false; - } - } - - @Override - protected - void - onPostExecute( - Boolean - done) { - Snackbar - s; - if (done) { - if (current - != null) { - ((TextView) - dialoglayout - .findViewById( - R - .id - .flair_text)) - .setText( - getString( - R - .string - .sidebar_flair, - current)); - } - s = - Snackbar - .make( - mToolbar, - R - .string - .snackbar_flair_success, - Snackbar - .LENGTH_SHORT); - } else { - s = - Snackbar - .make( - mToolbar, - R - .string - .snackbar_flair_error, - Snackbar - .LENGTH_SHORT); - } - if (s - != null) { - LayoutUtils - .showSnackbar( - s); - } - } - }.executeOnExecutor( - AsyncTask - .THREAD_POOL_EXECUTOR); - } - }) - .negativeText( - R - .string - .btn_cancel) - .show(); - } else { - new AsyncTask< - Void, - Void, - Boolean>() { - @Override - protected - Boolean - doInBackground( - Void - ... - params) { - try { - new ModerationManager( - Authentication - .reddit) - .setFlair( - subreddit, - t, - null, - Authentication - .name); - FlairTemplate - currentF = - m - .getCurrentFlair( - subreddit); - if (currentF.getText() - .isEmpty()) { - current = - ("[" - + currentF - .getCssClass() - + "]"); - } else { - current = - (currentF - .getText()); - } - return true; - } catch ( - Exception - e) { - e - .printStackTrace(); - return false; - } - } - - @Override - protected void - onPostExecute( - Boolean - done) { - Snackbar s; - if (done) { - if (current - != null) { - ((TextView) - dialoglayout - .findViewById( - R - .id - .flair_text)) - .setText( - getString( - R - .string - .sidebar_flair, - current)); - } - s = - Snackbar - .make( - mToolbar, - R - .string - .snackbar_flair_success, - Snackbar - .LENGTH_SHORT); - } else { - s = - Snackbar - .make( - mToolbar, - R - .string - .snackbar_flair_error, - Snackbar - .LENGTH_SHORT); - } - if (s - != null) { - LayoutUtils - .showSnackbar( - s); - } - } - }.executeOnExecutor( - AsyncTask - .THREAD_POOL_EXECUTOR); - } - } - }) - .show(); - } - }); - } - } - }; - currentFlair.execute((View) dialoglayout.findViewById(R.id.flair)); - } - } else { - if (drawerLayout != null) { - drawerLayout.setDrawerLockMode( - DrawerLayout.LOCK_MODE_LOCKED_CLOSED, GravityCompat.END); - } - } - } TimePeriod time = TimePeriod.DAY; @@ -2609,52 +1427,6 @@ public void onClick(DialogInterface dialogInterface, int i) { .show(); } - public void doSubSidebarNoLoad(final String subreddit) { - if (mAsyncGetSubreddit != null) { - mAsyncGetSubreddit.cancel(true); - } - - findViewById(R.id.loader).setVisibility(View.GONE); - - invalidateOptionsMenu(); - - if (!subreddit.equalsIgnoreCase("all") - && !subreddit.equalsIgnoreCase("frontpage") - && !subreddit.equalsIgnoreCase("friends") - && !subreddit.equalsIgnoreCase("mod") - && !subreddit.contains("+") - && !subreddit.contains(".") - && !subreddit.contains("/m/")) { - if (drawerLayout != null) { - drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, GravityCompat.END); - } - - findViewById(R.id.sidebar_text).setVisibility(View.GONE); - findViewById(R.id.sub_title).setVisibility(View.GONE); - findViewById(R.id.subscribers).setVisibility(View.GONE); - findViewById(R.id.active_users).setVisibility(View.GONE); - - findViewById(R.id.header_sub).setBackgroundColor(Palette.getColor(subreddit)); - ((TextView) findViewById(R.id.sub_infotitle)).setText(subreddit); - - // Sidebar buttons should use subreddit's accent color - int subColor = new ColorPreferences(this).getColor(subreddit); - ((TextView) findViewById(R.id.theme_text)).setTextColor(subColor); - ((TextView) findViewById(R.id.wiki_text)).setTextColor(subColor); - ((TextView) findViewById(R.id.post_text)).setTextColor(subColor); - ((TextView) findViewById(R.id.mods_text)).setTextColor(subColor); - ((TextView) findViewById(R.id.flair_text)).setTextColor(subColor); - ((TextView) drawerLayout.findViewById(R.id.sorting).findViewById(R.id.sort)) - .setTextColor(subColor); - ((TextView) findViewById(R.id.sync)).setTextColor(subColor); - - } else { - if (drawerLayout != null) { - drawerLayout.setDrawerLockMode( - DrawerLayout.LOCK_MODE_LOCKED_CLOSED, GravityCompat.END); - } - } - } /** * Starts the enter animations for various UI components of the toolbar subreddit search @@ -3089,7 +1861,7 @@ public void setDataSet(List data) { setToolbarClick(); setRecentBar(usedArray.get(toGoto)); - doSubSidebarNoLoad(usedArray.get(toGoto)); + sidebarController.doSubSidebarNoLoad(usedArray.get(toGoto)); } else if (NetworkUtil.isConnected(this)) { UserSubscriptions.doMainActivitySubs(this); } @@ -3292,7 +2064,7 @@ private IconCompat getIcon(String subreddit, @DrawableRes int overlay) { return IconCompat.createWithBitmap(color); } - private void changeSubscription(Subreddit subreddit, boolean isChecked) { + void changeSubscription(Subreddit subreddit, boolean isChecked) { // Made package-private currentlySubbed = isChecked; if (isChecked) { UserSubscriptions.addSubreddit( @@ -3313,46 +2085,6 @@ private void dismissProgressDialog() { } - private void setViews( - String rawHTML, - String subredditName, - SpoilerRobotoTextView firstTextView, - CommentOverflow commentOverflow) { - if (rawHTML.isEmpty()) { - return; - } - - List blocks = SubmissionParser.getBlocks(rawHTML); - - int startIndex = 0; - // the
case is when the body contains a table or code block first - if (!blocks.get(0).equals("
")) { - firstTextView.setVisibility(View.VISIBLE); - firstTextView.setTextHtml(blocks.get(0), subredditName); - firstTextView.setLinkTextColor(new ColorPreferences(this).getColor(subredditName)); - startIndex = 1; - } else { - firstTextView.setText(""); - firstTextView.setVisibility(View.GONE); - } - - if (blocks.size() > 1) { - if (startIndex == 0) { - commentOverflow.setViews(blocks, subredditName); - } else { - commentOverflow.setViews(blocks.subList(startIndex, blocks.size()), subredditName); - } - SidebarLayout sidebar = (SidebarLayout) findViewById(R.id.drawer_layout); - for (int i = 0; i < commentOverflow.getChildCount(); i++) { - View maybeScrollable = commentOverflow.getChildAt(i); - if (maybeScrollable instanceof HorizontalScrollView) { - sidebar.addScrollable(maybeScrollable); - } - } - } else { - commentOverflow.removeAllViews(); - } - } /** * If the user has the Subreddit Search method set to "long press on toolbar title", an diff --git a/app/src/main/java/me/edgan/redditslide/Activities/MainPagerAdapter.java b/app/src/main/java/me/edgan/redditslide/Activities/MainPagerAdapter.java index b9392dd7b..52ff8a8bc 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/MainPagerAdapter.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/MainPagerAdapter.java @@ -53,7 +53,7 @@ public void onPageScrolled( .setInterpolator(new LinearInterpolator()) .setDuration(180); if (position < mainActivity.usedArray.size()) { - mainActivity.doSubSidebarNoLoad(mainActivity.usedArray.get(position)); + mainActivity.sidebarController.doSubSidebarNoLoad(mainActivity.usedArray.get(position)); } } } diff --git a/app/src/main/java/me/edgan/redditslide/Activities/SidebarController.java b/app/src/main/java/me/edgan/redditslide/Activities/SidebarController.java new file mode 100644 index 000000000..db0b6409f --- /dev/null +++ b/app/src/main/java/me/edgan/redditslide/Activities/SidebarController.java @@ -0,0 +1,1046 @@ +package me.edgan.redditslide.Activities; + +import android.app.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.os.AsyncTask; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.CompoundButton; +import android.widget.HorizontalScrollView; +import android.widget.ImageView; +import android.widget.TextView; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.view.ContextThemeWrapper; +import androidx.appcompat.widget.AppCompatCheckBox; +import androidx.core.view.GravityCompat; +import androidx.drawerlayout.widget.DrawerLayout; + +import com.afollestad.materialdialogs.DialogAction; +import com.afollestad.materialdialogs.MaterialDialog; +import com.fasterxml.jackson.databind.JsonNode; +import com.google.android.material.snackbar.Snackbar; + +import net.dean.jraw.ApiException; +import net.dean.jraw.http.MultiRedditUpdateRequest; +import net.dean.jraw.http.NetworkException; +import net.dean.jraw.managers.AccountManager; +import net.dean.jraw.managers.ModerationManager; +import net.dean.jraw.managers.MultiRedditManager; +import net.dean.jraw.models.FlairTemplate; +import net.dean.jraw.models.MultiReddit; +import net.dean.jraw.models.MultiSubreddit; +import net.dean.jraw.models.Subreddit; +import net.dean.jraw.models.UserRecord; +import net.dean.jraw.paginators.Sorting; +import net.dean.jraw.paginators.TimePeriod; +import net.dean.jraw.paginators.UserRecordPaginator; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; + +import me.edgan.redditslide.Authentication; +import me.edgan.redditslide.Constants; +import me.edgan.redditslide.ImageFlairs; +import me.edgan.redditslide.Notifications.CheckForMail; +import me.edgan.redditslide.R; +import me.edgan.redditslide.Reddit; +import me.edgan.redditslide.SettingValues; +import me.edgan.redditslide.SpoilerRobotoTextView; +import me.edgan.redditslide.UserSubscriptions; +import me.edgan.redditslide.Views.CommentOverflow; +import me.edgan.redditslide.Views.SidebarLayout; +import me.edgan.redditslide.Visuals.ColorPreferences; +import me.edgan.redditslide.Visuals.Palette; +import me.edgan.redditslide.ui.settings.SettingsSubAdapter; +import me.edgan.redditslide.util.LayoutUtils; +import me.edgan.redditslide.util.MiscUtil; +import me.edgan.redditslide.util.OnSingleClickListener; +import me.edgan.redditslide.util.SortingUtil; +import me.edgan.redditslide.util.StringUtil; +import me.edgan.redditslide.util.SubmissionParser; + +public class SidebarController { + + private final MainActivity mainActivity; + private Sorting sorts; + private TimePeriod time = TimePeriod.DAY; + private AsyncTask currentFlair; + + public SidebarController(MainActivity mainActivity) { + this.mainActivity = mainActivity; + } + + public void doSubSidebar(final String subreddit) { + if (mainActivity.mAsyncGetSubreddit != null) { + mainActivity.mAsyncGetSubreddit.cancel(true); + } + mainActivity.findViewById(R.id.loader).setVisibility(View.VISIBLE); + + mainActivity.invalidateOptionsMenu(); + + if (!subreddit.equalsIgnoreCase("all") + && !subreddit.equalsIgnoreCase("frontpage") + && !subreddit.equalsIgnoreCase("friends") + && !subreddit.equalsIgnoreCase("mod") + && !subreddit.contains("+") + && !subreddit.contains(".") + && !subreddit.contains("/m/")) { + if (mainActivity.drawerLayout != null) { + mainActivity.drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, GravityCompat.END); + } + + mainActivity.mAsyncGetSubreddit = new AsyncGetSubredditTask(mainActivity); + mainActivity.mAsyncGetSubreddit.execute(subreddit); + + final View dialoglayout = mainActivity.findViewById(R.id.sidebarsub); + { + View submit = (dialoglayout.findViewById(R.id.submit)); + + if (!Authentication.isLoggedIn || !Authentication.didOnline) { + submit.setVisibility(View.GONE); + } + if (SettingValues.fab && SettingValues.fabType == Constants.FAB_POST) { + submit.setVisibility(View.GONE); + } + + submit.setOnClickListener( + new OnSingleClickListener() { + @Override + public void onSingleClick(View view) { + Intent inte = new Intent(mainActivity, Submit.class); + if (!subreddit.contains("/m/") && mainActivity.canSubmit) { + inte.putExtra(Submit.EXTRA_SUBREDDIT, subreddit); + } + mainActivity.startActivity(inte); + } + } + ); + } + + dialoglayout + .findViewById(R.id.wiki) + .setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View v) { + Intent i = new Intent(mainActivity, Wiki.class); + i.putExtra(Wiki.EXTRA_SUBREDDIT, subreddit); + mainActivity.startActivity(i); + } + } + ); + dialoglayout + .findViewById(R.id.syncflair) + .setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View v) { + ImageFlairs.syncFlairs(mainActivity, subreddit); + } + } + ); + dialoglayout + .findViewById(R.id.submit) + .setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View v) { + Intent i = new Intent(mainActivity, Submit.class); + if ((!subreddit.contains("/m/") || !subreddit.contains(".")) && mainActivity.canSubmit) { + i.putExtra(Submit.EXTRA_SUBREDDIT, subreddit); + } + mainActivity.startActivity(i); + } + } + ); + + final TextView sort = dialoglayout.findViewById(R.id.sort); + Sorting sortingis = Sorting.HOT; + if (SettingValues.hasSort(subreddit)) { + sortingis = SettingValues.getBaseSubmissionSort(subreddit); + sort.setText( + sortingis.name() + ( + (sortingis == Sorting.CONTROVERSIAL || sortingis == Sorting.TOP) + ? " of " + SettingValues.getBaseTimePeriod(subreddit).name() + : "")); + } else { + sort.setText("Set default sorting"); + } + final int sortid = SortingUtil.getSortingId(sortingis); + dialoglayout + .findViewById(R.id.sorting) + .setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View v) { + final DialogInterface.OnClickListener l2 = + new DialogInterface.OnClickListener() { + @Override + public void onClick( + DialogInterface dialogInterface, int i) { + switch (i) { + case 0: + sorts = Sorting.HOT; + break; + case 1: + sorts = Sorting.NEW; + break; + case 2: + sorts = Sorting.RISING; + break; + case 3: + sorts = Sorting.TOP; + askTimePeriod(sorts, subreddit, dialoglayout); + return; + case 4: + sorts = Sorting.CONTROVERSIAL; + askTimePeriod(sorts, subreddit, dialoglayout); + return; + } + + SettingValues.setSubSorting( + sorts, time, subreddit); + Sorting sortingis = SettingValues.getBaseSubmissionSort(subreddit); + sort.setText( + sortingis.name()+ ( + (sortingis == Sorting.CONTROVERSIAL || sortingis == Sorting.TOP) + ? " of "+ SettingValues.getBaseTimePeriod(subreddit).name() + : "" + ) + ); + mainActivity.reloadSubs(); + } + }; + + new AlertDialog.Builder(mainActivity) + .setTitle(R.string.sorting_choose) + .setSingleChoiceItems(SortingUtil.getSortingStrings(), sortid, l2) + .setNegativeButton( + "Reset default sorting", + (dialog, which) -> { + SettingValues.prefs.edit().remove("defaultSort" + subreddit.toLowerCase(Locale.ENGLISH)).apply(); + SettingValues.prefs.edit().remove("defaultTime" + subreddit.toLowerCase(Locale.ENGLISH)).apply(); + final TextView sort1 = dialoglayout.findViewById(R.id.sort); + + if (SettingValues.hasSort(subreddit)) { + Sorting sortingis1 = SettingValues.getBaseSubmissionSort(subreddit); + sort1.setText(sortingis1.name() + + ((sortingis1 == Sorting.CONTROVERSIAL || sortingis1 == Sorting.TOP) + ? " of " + SettingValues.getBaseTimePeriod(subreddit).name() : "")); + } else { + sort1.setText("Set default sorting"); + } + + mainActivity.reloadSubs(); + }) + .show(); + } + } + ); + + dialoglayout + .findViewById(R.id.theme) + .setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View v) { + int style = new ColorPreferences(mainActivity).getThemeSubreddit(subreddit); + final Context contextThemeWrapper = new ContextThemeWrapper(mainActivity, style); + LayoutInflater localInflater = mainActivity.getLayoutInflater().cloneInContext(contextThemeWrapper); + final View dialoglayout = localInflater.inflate(R.layout.colorsub, null); + ArrayList arrayList = new ArrayList<>(); + arrayList.add(subreddit); + SettingsSubAdapter.showSubThemeEditor(arrayList, mainActivity, dialoglayout); + } + }); + dialoglayout + .findViewById(R.id.mods) + .setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View v) { + final Dialog d = + new MaterialDialog.Builder(mainActivity) + .title(R.string.sidebar_findingmods) + .cancelable(true) + .content(R.string.misc_please_wait) + .progress(true, 100) + .show(); + new AsyncTask() { + ArrayList mods; + + @Override + protected Void doInBackground(Void... params) { + mods = new ArrayList<>(); + UserRecordPaginator paginator = new UserRecordPaginator(Authentication.reddit, subreddit, "moderators"); + paginator.setSorting(Sorting.HOT); + paginator.setTimePeriod(TimePeriod.ALL); + + while (paginator.hasNext()) { + mods.addAll(paginator.next()); + } + + return null; + } + + @Override + protected void onPostExecute(Void aVoid) { + final ArrayList names = new ArrayList<>(); + for (UserRecord rec : mods) { + names.add(rec.getFullName()); + } + d.dismiss(); + new MaterialDialog.Builder(mainActivity) + .title(mainActivity.getString(R.string.sidebar_submods, subreddit)) + .items(names) + .itemsCallback( + new MaterialDialog.ListCallback() { + @Override + public void onSelection(MaterialDialog dialog, View itemView, int which, CharSequence text) { + Intent i = new Intent(mainActivity, Profile.class); + i.putExtra(Profile.EXTRA_PROFILE, names.get(which)); + mainActivity.startActivity(i); + } + }) + .positiveText(R.string.btn_message) + .onPositive( + new MaterialDialog.SingleButtonCallback() { + @Override + public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { + Intent i = new Intent(mainActivity, SendMessage.class); + i.putExtra(SendMessage.EXTRA_NAME, "/r/" + subreddit); + mainActivity.startActivity(i); + } + }) + .show(); + } + }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + }); + + dialoglayout.findViewById(R.id.flair).setVisibility(View.GONE); + + if (Authentication.didOnline && Authentication.isLoggedIn) { + if (currentFlair != null) currentFlair.cancel(true); + currentFlair = + new AsyncTask() { + List flairs; + ArrayList flairText; + String current; + AccountManager m; + + @Override + protected View doInBackground(View... params) { + try { + m = new AccountManager(Authentication.reddit); + JsonNode node = m.getFlairChoicesRootNode(subreddit, null); + flairs = m.getFlairChoices(subreddit, node); + + FlairTemplate currentF = m.getCurrentFlair(subreddit, node); + + if (currentF != null) { + if (currentF.getText().isEmpty()) { + current = ("[" + currentF.getCssClass() + "]"); + } else { + current = (currentF.getText()); + } + } + + flairText = new ArrayList<>(); + + for (FlairTemplate temp : flairs) { + if (temp.getText().isEmpty()) { + flairText.add("[" + temp.getCssClass() + "]"); + } else { + flairText.add(temp.getText()); + } + } + } catch (Exception e1) { + e1.printStackTrace(); + } + + return params[0]; + } + + @Override + protected void onPostExecute(View flair) { + if (flairs != null && !flairs.isEmpty() && flairText != null && !flairText.isEmpty()) { + flair.setVisibility(View.VISIBLE); + + if (current != null) { + ((TextView) dialoglayout.findViewById(R.id.flair_text)) + .setText(mainActivity.getString(R.string.sidebar_flair, current)); + } + + flair.setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View v) { + new MaterialDialog.Builder(mainActivity) + .items(flairText) + .title(R.string.sidebar_select_flair) + .itemsCallback( + new MaterialDialog.ListCallback() { + @Override + public void onSelection( + MaterialDialog dialog, View itemView, int which, CharSequence text) { + final FlairTemplate t = flairs.get(which); + if (t.isTextEditable()) { + new MaterialDialog.Builder(mainActivity) + .title(R.string.sidebar_select_flair_text) + .input(mainActivity.getString(R.string.mod_flair_hint), t.getText(), true, (dialog1, input) -> {}) + .positiveText(R.string.btn_set) + .onPositive( + new MaterialDialog.SingleButtonCallback() { + @Override + public + void onClick(MaterialDialog dialog, DialogAction which) { + final String flair = dialog.getInputEditText().getText().toString(); + new AsyncTask() { + @Override + protected + Boolean doInBackground(Void... params) { + try { + new ModerationManager(Authentication.reddit) + .setFlair(subreddit, t, flair, Authentication.name); + FlairTemplate currentF = m.getCurrentFlair(subreddit); + + if (currentF.getText().isEmpty()) { + current = ("[" + currentF.getCssClass() + "]"); + } else { + current = (currentF.getText()); + } + + return true; + } catch (Exception e) { + e.printStackTrace(); + + return false; + } + } + + @Override + protected + void onPostExecute(Boolean done) { + Snackbar s; + if (done) { + if (current != null) { + ((TextView) dialoglayout.findViewById(R.id.flair_text)) + .setText(mainActivity.getString(R.string.sidebar_flair, current)); + } + + s = Snackbar.make( + mainActivity.mToolbar,R.string.snackbar_flair_success, + Snackbar.LENGTH_SHORT + ); + } else { + s = Snackbar.make( + mainActivity.mToolbar, R.string.snackbar_flair_error, + Snackbar.LENGTH_SHORT + ); + } + if (s != null) { + LayoutUtils.showSnackbar(s); + } + } + }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + }) + .negativeText(R.string.btn_cancel) + .show(); + } else { + new AsyncTask() { + @Override + protected Boolean doInBackground(Void... params) { + try { + new ModerationManager(Authentication.reddit).setFlair(subreddit, t, null, Authentication.name); + FlairTemplate currentF = m.getCurrentFlair(subreddit); + + if (currentF.getText().isEmpty()) { + current = ("[" + currentF.getCssClass() + "]"); + } else { + current = (currentF.getText()); + } + + return true; + } catch (Exception e) { + e.printStackTrace(); + return false; + } + } + + @Override + protected void onPostExecute(Boolean done) { + Snackbar s; + if (done) { + if (current != null) { + ((TextView) dialoglayout + .findViewById(R.id.flair_text)) + .setText(mainActivity.getString(R.string.sidebar_flair,current)); + } + s = Snackbar.make( + mainActivity.mToolbar, + R.string.snackbar_flair_success, + Snackbar.LENGTH_SHORT + ); + } else { + s = Snackbar.make( + mainActivity.mToolbar, R.string.snackbar_flair_error, + Snackbar.LENGTH_SHORT + ); + } + + if (s != null) { + LayoutUtils.showSnackbar(s); + } + } + }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + } + }) + .show(); + } + }); + } + } + }; + currentFlair.execute((View) dialoglayout.findViewById(R.id.flair)); + } + } else { + if (mainActivity.drawerLayout != null) { + mainActivity.drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, GravityCompat.END); + } + } + } + + private void askTimePeriod(final Sorting sort, final String sub, final View dialoglayout) { + final DialogInterface.OnClickListener l2 = + new DialogInterface.OnClickListener() { + + @Override + public void onClick(DialogInterface dialogInterface, int i) { + switch (i) { + case 0: + time = TimePeriod.HOUR; + break; + case 1: + time = TimePeriod.DAY; + break; + case 2: + time = TimePeriod.WEEK; + break; + case 3: + time = TimePeriod.MONTH; + break; + case 4: + time = TimePeriod.YEAR; + break; + case 5: + time = TimePeriod.ALL; + break; + } + SettingValues.setSubSorting(sort, time, sub); + SortingUtil.setSorting(sub, sort); + SortingUtil.setTime(sub, time); + final TextView sortTextView = dialoglayout.findViewById(R.id.sort); + if (SettingValues.hasSort(sub)) { + Sorting sortingis = SettingValues.getBaseSubmissionSort(sub); + sortTextView.setText( + sortingis.name() + ((sortingis == Sorting.CONTROVERSIAL || sortingis == Sorting.TOP) + ? " of " + SettingValues.getBaseTimePeriod(sub).name() + : "")); + } else { + sortTextView.setText("Set default sorting"); + } + mainActivity.reloadSubs(); + } + }; + + new AlertDialog.Builder(mainActivity) + .setTitle(R.string.sorting_choose) + .setSingleChoiceItems(SortingUtil.getSortingTimesStrings(), SortingUtil.getSortingTimeId(""), l2) + .show(); + } + + public void doSubSidebarNoLoad(final String subreddit) { + if (mainActivity.mAsyncGetSubreddit != null) { + mainActivity.mAsyncGetSubreddit.cancel(true); + } + + mainActivity.findViewById(R.id.loader).setVisibility(View.GONE); + + mainActivity.invalidateOptionsMenu(); + + if (!subreddit.equalsIgnoreCase("all") + && !subreddit.equalsIgnoreCase("frontpage") + && !subreddit.equalsIgnoreCase("friends") + && !subreddit.equalsIgnoreCase("mod") + && !subreddit.contains("+") + && !subreddit.contains(".") + && !subreddit.contains("/m/")) { + if (mainActivity.drawerLayout != null) { + mainActivity.drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, GravityCompat.END); + } + + mainActivity.findViewById(R.id.sidebar_text).setVisibility(View.GONE); + mainActivity.findViewById(R.id.sub_title).setVisibility(View.GONE); + mainActivity.findViewById(R.id.subscribers).setVisibility(View.GONE); + mainActivity.findViewById(R.id.active_users).setVisibility(View.GONE); + + mainActivity.findViewById(R.id.header_sub).setBackgroundColor(Palette.getColor(subreddit)); + ((TextView) mainActivity.findViewById(R.id.sub_infotitle)).setText(subreddit); + + // Sidebar buttons should use subreddit's accent color + int subColor = new ColorPreferences(mainActivity).getColor(subreddit); + ((TextView) mainActivity.findViewById(R.id.theme_text)).setTextColor(subColor); + ((TextView) mainActivity.findViewById(R.id.wiki_text)).setTextColor(subColor); + ((TextView) mainActivity.findViewById(R.id.post_text)).setTextColor(subColor); + ((TextView) mainActivity.findViewById(R.id.mods_text)).setTextColor(subColor); + ((TextView) mainActivity.findViewById(R.id.flair_text)).setTextColor(subColor); + ((TextView) mainActivity.drawerLayout.findViewById(R.id.sorting).findViewById(R.id.sort)).setTextColor(subColor); + ((TextView) mainActivity.findViewById(R.id.sync)).setTextColor(subColor); + + } else { + if (mainActivity.drawerLayout != null) { + mainActivity.drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, GravityCompat.END); + } + } + } + + public void doSubOnlyStuff(final Subreddit subreddit) { + mainActivity.findViewById(R.id.loader).setVisibility(View.GONE); + if (subreddit.getSubredditType() != null) { + mainActivity.canSubmit = !subreddit.getSubredditType().equals("RESTRICTED"); + } else { + mainActivity.canSubmit = true; + } + if (subreddit.getSidebar() != null && !subreddit.getSidebar().isEmpty()) { + mainActivity.findViewById(R.id.sidebar_text).setVisibility(View.VISIBLE); + + final String text = subreddit.getDataNode().get("description_html").asText().trim(); + setViews(text, subreddit.getDisplayName(), mainActivity.sidebarBody, mainActivity.sidebarOverflow); + + // get all subs that have Notifications enabled + ArrayList rawSubs = StringUtil.stringToArray(Reddit.appRestart.getString(CheckForMail.SUBS_TO_GET, "")); + HashMap subThresholds = new HashMap<>(); + for (String s : rawSubs) { + try { + String[] split = s.split(":"); + subThresholds.put(split[0].toLowerCase(Locale.ENGLISH), Integer.valueOf(split[1])); + } catch (Exception ignored) { + // do nothing + } + } + + // whether or not this subreddit was in the keySet + boolean isNotified =subThresholds.containsKey(subreddit.getDisplayName().toLowerCase(Locale.ENGLISH)); + ((AppCompatCheckBox) mainActivity.findViewById(R.id.notify_posts_state)).setChecked(isNotified); + } else { + mainActivity.findViewById(R.id.sidebar_text).setVisibility(View.GONE); + } + { + View collection = mainActivity.findViewById(R.id.collection); + if (Authentication.isLoggedIn) { + collection.setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View v) { + new AsyncTask() { + HashMap multis = new HashMap(); + + @Override + protected Void doInBackground(Void... params) { + if (UserSubscriptions.multireddits == null) { + UserSubscriptions.syncMultiReddits(mainActivity); + } + + for (MultiReddit r : UserSubscriptions.multireddits) { + multis.put(r.getDisplayName(), r); + } + + return null; + } + + @Override + protected void onPostExecute(Void aVoid) { + new MaterialDialog.Builder(mainActivity) + .title(mainActivity.getString(R.string.multi_add_to, subreddit.getDisplayName())) + .items(multis.keySet()) + .itemsCallback( + new MaterialDialog.ListCallback() { + @Override + public void onSelection(MaterialDialog dialog, View itemView, final int which, CharSequence text) { + new AsyncTask() { + @Override + protected Void doInBackground(Void... params) { + try { + final String multiName = multis.keySet().toArray(new String [0])[which]; + List subs = new ArrayList(); + + for (MultiSubreddit sub : multis.get(multiName).getSubreddits()) { + subs.add(sub.getDisplayName()); + } + + subs.add(subreddit.getDisplayName()); + new MultiRedditManager(Authentication.reddit) + .createOrUpdate(new MultiRedditUpdateRequest + .Builder(Authentication.name,multiName) + .subreddits(subs).build() + ); + + UserSubscriptions.syncMultiReddits(mainActivity); + + mainActivity.runOnUiThread( + new Runnable() { + @Override + public void run() { + mainActivity.drawerLayout.closeDrawers(); + + Snackbar s = Snackbar.make( + mainActivity.mToolbar, + mainActivity.getString(R.string.multi_subreddit_added,multiName), + Snackbar.LENGTH_LONG + ); + + LayoutUtils.showSnackbar(s); + } + }); + } catch (final + NetworkException + | ApiException e) { + mainActivity.runOnUiThread( + new Runnable() { + @Override + public void run() { + mainActivity.runOnUiThread( + new Runnable() { + @Override + public void run() { + Snackbar.make( + mainActivity.mToolbar, + mainActivity.getString(R.string.multi_error), + Snackbar.LENGTH_LONG + ) + .setAction(R.string.btn_ok, null) + .show(); + } + }); + } + }); + e.printStackTrace(); + } + return null; + } + }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + } + ) + .show(); + } + }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + }); + } else { + collection.setVisibility(View.GONE); + } + } + { + final AppCompatCheckBox notifyStateCheckBox = + (AppCompatCheckBox) mainActivity.findViewById(R.id.notify_posts_state); + assert notifyStateCheckBox != null; + + notifyStateCheckBox.setOnCheckedChangeListener( + new CompoundButton.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { + if (isChecked) { + final String sub = subreddit.getDisplayName(); + + if (!sub.equalsIgnoreCase("all") + && !sub.equalsIgnoreCase("frontpage") + && !sub.equalsIgnoreCase("friends") + && !sub.equalsIgnoreCase("mod") + && !sub.contains("+") + && !sub.contains(".") + && !sub.contains("/m/")) { + new AlertDialog.Builder(mainActivity) + .setTitle(mainActivity.getString(R.string.sub_post_notifs_title, sub)) + .setMessage(R.string.sub_post_notifs_msg) + .setPositiveButton( + R.string.btn_ok, + (dialog, which) -> + new MaterialDialog.Builder(mainActivity) + .title(R.string.sub_post_notifs_threshold) + .items( + new String[] { + "1", "5", "10", + "20", "40", "50" + } + ) + .alwaysCallSingleChoiceCallback() + .itemsCallbackSingleChoice( + 0, + new MaterialDialog + .ListCallbackSingleChoice() { + @Override + public boolean onSelection(MaterialDialog dialog, View itemView, int which, CharSequence text) { + ArrayList subs = StringUtil.stringToArray( + Reddit.appRestart.getString(CheckForMail.SUBS_TO_GET, "") + ); + subs.add(sub + ":" + text); + Reddit.appRestart.edit().putString( + CheckForMail.SUBS_TO_GET, + StringUtil.arrayToString(subs) + ).commit(); + + return true; + } + } + ) + .cancelable(false) + .show()) + .setNegativeButton(R.string.btn_cancel, null) + .setNegativeButton( + R.string.btn_cancel, + (dialog, which) -> notifyStateCheckBox.setChecked(false) + ) + .setOnCancelListener(dialog -> notifyStateCheckBox.setChecked(false)) + .show(); + } else { + notifyStateCheckBox.setChecked(false); + Toast.makeText(mainActivity, R.string.sub_post_notifs_err, Toast.LENGTH_SHORT).show(); + } + } else { + Intent cancelIntent = new Intent(mainActivity, CancelSubNotifs.class); + cancelIntent.putExtra(CancelSubNotifs.EXTRA_SUB, subreddit.getDisplayName()); + mainActivity.startActivity(cancelIntent); + } + } + }); + } + { + final TextView subscribe = (TextView) mainActivity.findViewById(R.id.subscribe); + mainActivity.currentlySubbed = + (!Authentication.isLoggedIn && mainActivity.usedArray.contains(subreddit.getDisplayName().toLowerCase(Locale.ENGLISH))) + || subreddit.isUserSubscriber(); + + MiscUtil.doSubscribeButtonText(mainActivity.currentlySubbed, subscribe); + + assert subscribe != null; + subscribe.setOnClickListener( + new View.OnClickListener() { + private void doSubscribe() { + if (Authentication.isLoggedIn) { + new AlertDialog.Builder(mainActivity) + .setTitle(mainActivity.getString(R.string.subscribe_to, subreddit.getDisplayName())) + .setPositiveButton( + R.string.reorder_add_subscribe, + (dialog, which) -> new AsyncTask() { + @Override + public void onPostExecute( + Boolean success) { + if (!success) { // If subreddit was removed from account or not + new AlertDialog.Builder(mainActivity) + .setTitle(R.string.force_change_subscription) + .setMessage(R.string.force_change_subscription_desc) + .setPositiveButton( + R.string.btn_yes, + (dialog1, which1) -> { + mainActivity.changeSubscription( subreddit, true); // Force add the subscription + Snackbar s = Snackbar.make( + mainActivity.mToolbar, mainActivity.getString(R.string.misc_subscribed), + Snackbar.LENGTH_LONG + ); + LayoutUtils.showSnackbar(s); + }) + .setNegativeButton(R.string.btn_no, null) + .setCancelable(false) + .show(); + } else { + mainActivity.changeSubscription( + subreddit, true); + } + } + + @Override + protected Boolean doInBackground( + Void... params) { + try { + new AccountManager(Authentication.reddit).subscribe(subreddit); + } catch (NetworkException e) { + return false; // Either network crashed or trying to unsubscribe to a subreddit that the account isn't subscribed to + } + return true; + } + }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR)) + .setNeutralButton( + R.string.btn_add_to_sublist, + (dialog, which) -> { + mainActivity.changeSubscription(subreddit, true); // Force add the subscription + Snackbar s = Snackbar.make(mainActivity.mToolbar, R.string.sub_added, Snackbar.LENGTH_LONG); + LayoutUtils.showSnackbar(s); + }) + .setNegativeButton(R.string.btn_cancel, null) + .show(); + } else { + mainActivity.changeSubscription(subreddit, true); + } + } + + private void doUnsubscribe() { + if (Authentication.didOnline) { + new AlertDialog.Builder(mainActivity) + .setTitle(mainActivity.getString(R.string.unsubscribe_from, subreddit.getDisplayName())) + .setPositiveButton( + R.string.reorder_remove_unsubscribe, + (dialog, which) -> new AsyncTask() { + @Override + public void onPostExecute(Boolean success) { + if (!success) { // If subreddit was remove from account or not + new AlertDialog.Builder(mainActivity) + .setTitle(R.string.force_change_subscription) + .setMessage(R.string.force_change_subscription_desc) + .setPositiveButton(R.string.btn_yes, + (dialog12, which12) -> { + mainActivity.changeSubscription(subreddit, false); // Force add the subscription + Snackbar s = Snackbar.make( + mainActivity.mToolbar, + mainActivity.getString(R.string.misc_unsubscribed), + Snackbar.LENGTH_LONG + ); + LayoutUtils.showSnackbar(s); + } + ) + .setNegativeButton(R.string.btn_no, null) + .setCancelable(false) + .show(); + } else { + mainActivity.changeSubscription(subreddit, false); + } + } + + @Override + protected Boolean doInBackground( + Void... params) { + try { + new AccountManager(Authentication.reddit).unsubscribe(subreddit); + } catch (NetworkException e) { + return false; // Either network crashed or trying to unsubscribe to a subreddit that the account isn't subscribed to + } + return true; + } + }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR)) + .setNeutralButton( + R.string.just_unsub, + (dialog, which) -> { mainActivity.changeSubscription(subreddit, false); // Force add the subscription + Snackbar s = Snackbar.make(mainActivity.mToolbar, R.string.misc_unsubscribed, Snackbar.LENGTH_LONG); + LayoutUtils.showSnackbar(s); + } + ) + .setNegativeButton(R.string.btn_cancel, null) + .show(); + } else { + mainActivity.changeSubscription(subreddit, false); + } + } + + @Override + public void onClick(View v) { + if (!mainActivity.currentlySubbed) { + doSubscribe(); + } else { + doUnsubscribe(); + } + MiscUtil.doSubscribeButtonText(mainActivity.currentlySubbed, subscribe); + } + }); + } + if (!subreddit.getPublicDescription().isEmpty()) { + mainActivity.findViewById(R.id.sub_title).setVisibility(View.VISIBLE); + setViews( + subreddit.getDataNode().get("public_description_html").asText(), + subreddit.getDisplayName().toLowerCase(Locale.ENGLISH), + ((SpoilerRobotoTextView) mainActivity.findViewById(R.id.sub_title)), + (CommentOverflow) mainActivity.findViewById(R.id.sub_title_overflow) + ); + } else { + mainActivity.findViewById(R.id.sub_title).setVisibility(View.GONE); + } + ((ImageView) mainActivity.findViewById(R.id.subimage)).setImageResource(0); + if (subreddit.getDataNode().has("icon_img") && !subreddit.getDataNode().get("icon_img").asText().isEmpty()) { + mainActivity.findViewById(R.id.subimage).setVisibility(View.VISIBLE); + ((Reddit) mainActivity.getApplication()) + .getImageLoader() + .displayImage( + subreddit.getDataNode().get("icon_img").asText(), + (ImageView) mainActivity.findViewById(R.id.subimage) + ); + } else { + mainActivity.findViewById(R.id.subimage).setVisibility(View.GONE); + } + String bannerImage = subreddit.getBannerImage(); + if (bannerImage != null && !bannerImage.isEmpty()) { + mainActivity.findViewById(R.id.sub_banner).setVisibility(View.VISIBLE); + ((Reddit) mainActivity.getApplication()) + .getImageLoader() + .displayImage(bannerImage, (ImageView) mainActivity.findViewById(R.id.sub_banner)); + } else { + mainActivity.findViewById(R.id.sub_banner).setVisibility(View.GONE); + } + ((TextView) mainActivity.findViewById(R.id.subscribers)) + .setText(mainActivity.getString(R.string.subreddit_subscribers_string,subreddit.getLocalizedSubscriberCount())); + mainActivity.findViewById(R.id.subscribers).setVisibility(View.VISIBLE); + + ((TextView) mainActivity.findViewById(R.id.active_users)) + .setText(mainActivity.getString(R.string.subreddit_active_users_string_new, subreddit.getLocalizedAccountsActive())); + mainActivity.findViewById(R.id.active_users).setVisibility(View.VISIBLE); + } + + private void setViews( + String rawHTML, + String subredditName, + SpoilerRobotoTextView firstTextView, + CommentOverflow commentOverflow) { + if (rawHTML.isEmpty()) { + return; + } + + List blocks = SubmissionParser.getBlocks(rawHTML); + + int startIndex = 0; + // the
case is when the body contains a table or code block first + if (!blocks.get(0).equals("
")) { + firstTextView.setVisibility(View.VISIBLE); + firstTextView.setTextHtml(blocks.get(0), subredditName); + firstTextView.setLinkTextColor(new ColorPreferences(mainActivity).getColor(subredditName)); + startIndex = 1; + } else { + firstTextView.setText(""); + firstTextView.setVisibility(View.GONE); + } + + if (blocks.size() > 1) { + if (startIndex == 0) { + commentOverflow.setViews(blocks, subredditName); + } else { + commentOverflow.setViews(blocks.subList(startIndex, blocks.size()), subredditName); + } + SidebarLayout sidebar = (SidebarLayout) mainActivity.findViewById(R.id.drawer_layout); + for (int i = 0; i < commentOverflow.getChildCount(); i++) { + View maybeScrollable = commentOverflow.getChildAt(i); + if (maybeScrollable instanceof HorizontalScrollView) { + sidebar.addScrollable(maybeScrollable); + } + } + } else { + commentOverflow.removeAllViews(); + } + } +} \ No newline at end of file From d26519da51b3dabccc648ec804ee7ad708e79a55 Mon Sep 17 00:00:00 2001 From: Nathan Grennan Date: Mon, 31 Mar 2025 00:15:02 -0700 Subject: [PATCH 08/99] Broke out SidebarActions class from MainActivity.java --- .../Activities/AsyncGetSubredditTask.java | 17 ++--- .../Activities/DrawerController.java | 2 +- .../redditslide/Activities/MainActivity.java | 32 ++------- .../Activities/MainPagerAdapterComment.java | 4 +- .../Activities/SidebarActions.java | 28 ++++++++ .../Activities/SidebarController.java | 71 +++++++++++++------ 6 files changed, 92 insertions(+), 62 deletions(-) create mode 100644 app/src/main/java/me/edgan/redditslide/Activities/SidebarActions.java diff --git a/app/src/main/java/me/edgan/redditslide/Activities/AsyncGetSubredditTask.java b/app/src/main/java/me/edgan/redditslide/Activities/AsyncGetSubredditTask.java index 308dc7843..fc69e443d 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/AsyncGetSubredditTask.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/AsyncGetSubredditTask.java @@ -12,22 +12,23 @@ */ public class AsyncGetSubredditTask extends AsyncTask { - private MainActivity mainActivity; + private SidebarController sidebarController; - public AsyncGetSubredditTask(MainActivity activity) { - this.mainActivity = activity; + public AsyncGetSubredditTask(SidebarController controller) { + this.sidebarController = controller; } @Override public void onPostExecute(Subreddit subreddit) { // Ensure mainActivity is still valid before calling its method - if (mainActivity != null && !mainActivity.isDestroyed() && subreddit != null) { - mainActivity.sidebarController.doSubOnlyStuff(subreddit); - } else if (mainActivity != null && !mainActivity.isDestroyed()) { + // Ensure sidebarController and its activity are still valid + if (sidebarController != null && sidebarController.isActivityValid() && subreddit != null) { + sidebarController.doSubOnlyStuff(subreddit); + } else if (sidebarController != null && sidebarController.isActivityValid()) { // Handle cases where subreddit is null (e.g., network error, subreddit not found) Log.w(LogUtil.getTag(), "Failed to fetch subreddit details or subreddit is null."); - // Optionally, inform the user or update UI accordingly in MainActivity - // mainActivity.handleSubredditFetchError(); + // Optionally, inform the user or update UI accordingly via SidebarController + // sidebarController.handleSubredditFetchError(); } } diff --git a/app/src/main/java/me/edgan/redditslide/Activities/DrawerController.java b/app/src/main/java/me/edgan/redditslide/Activities/DrawerController.java index 2589fa970..19d469afe 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/DrawerController.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/DrawerController.java @@ -800,7 +800,7 @@ public void onSingleClick(View v) { Intent i = new Intent(mainActivity, SettingsActivity.class); mainActivity.startActivity(i); // Cancel sub loading because exiting the settings will reload it anyway - if (mainActivity.mAsyncGetSubreddit != null) mainActivity.mAsyncGetSubreddit.cancel(true); + if (mainActivity.sidebarController != null) mainActivity.sidebarController.cancelAsyncGetSubredditTask(); mainActivity.drawerLayout.closeDrawers(); } }); diff --git a/app/src/main/java/me/edgan/redditslide/Activities/MainActivity.java b/app/src/main/java/me/edgan/redditslide/Activities/MainActivity.java index c9b89f860..f499685ec 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/MainActivity.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/MainActivity.java @@ -1,6 +1,5 @@ package me.edgan.redditslide.Activities; - import android.app.Activity; import android.app.Dialog; import android.content.Context; @@ -84,12 +83,10 @@ import me.edgan.redditslide.R; import me.edgan.redditslide.Reddit; import me.edgan.redditslide.SettingValues; -import me.edgan.redditslide.SpoilerRobotoTextView; import me.edgan.redditslide.Synccit.MySynccitUpdateTask; import me.edgan.redditslide.Synccit.SynccitRead; import me.edgan.redditslide.UserSubscriptions; import me.edgan.redditslide.Views.CatchStaggeredGridLayoutManager; -import me.edgan.redditslide.Views.CommentOverflow; import me.edgan.redditslide.Views.PreCachingLayoutManager; import me.edgan.redditslide.Views.ToggleSwipeViewPager; import me.edgan.redditslide.Visuals.ColorPreferences; @@ -113,7 +110,6 @@ import net.dean.jraw.managers.AccountManager; import net.dean.jraw.models.MultiReddit; import net.dean.jraw.models.Submission; -import net.dean.jraw.models.Subreddit; import net.dean.jraw.paginators.Sorting; import net.dean.jraw.paginators.SubredditPaginator; import net.dean.jraw.paginators.TimePeriod; @@ -179,16 +175,12 @@ public class MainActivity extends BaseActivity String term; View headerMain; MaterialDialog d; - public AsyncTask currentFlair; // Made public - public SpoilerRobotoTextView sidebarBody; // Made public - public CommentOverflow sidebarOverflow; // Made public + public AsyncTask currentFlair; View accountsArea; SideArrayAdapter sideArrayAdapter; Menu menu; AsyncTask caching; - public boolean currentlySubbed; // Made public int back; - public AsyncGetSubredditTask mAsyncGetSubreddit = null; // Made public int headerHeight; public int reloadItemNumber = -2; private static final int NOTIFICATION_PERMISSION_REQUEST_CODE = 1001; @@ -197,7 +189,8 @@ public class MainActivity extends BaseActivity DrawerController drawerController; public ToolbarSearchController toolbarSearchController; - SidebarController sidebarController; // Added declaration + SidebarController sidebarController; + SidebarActions sidebarActions; @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == SETTINGS_RESULT) { @@ -940,6 +933,7 @@ public void onSingleClick( drawerController = new DrawerController(this); toolbarSearchController = new ToolbarSearchController(this); sidebarController = new SidebarController(this); // Added initialization + sidebarActions = new SidebarActions(this); rootView = findViewById(android.R.id.content); @@ -996,10 +990,6 @@ public void onGlobalLayout() { pager.setSwipingEnabled(false); } - sidebarBody = (SpoilerRobotoTextView) findViewById(R.id.sidebar_text); - - sidebarOverflow = (CommentOverflow) findViewById(R.id.commentOverflow); - if (!Reddit.appRestart.getBoolean("isRestarting", false) && Reddit.colors.contains("Tutorial")) { @@ -2064,20 +2054,6 @@ private IconCompat getIcon(String subreddit, @DrawableRes int overlay) { return IconCompat.createWithBitmap(color); } - void changeSubscription(Subreddit subreddit, boolean isChecked) { // Made package-private - currentlySubbed = isChecked; - if (isChecked) { - UserSubscriptions.addSubreddit( - subreddit.getDisplayName().toLowerCase(Locale.ENGLISH), MainActivity.this); - } else { - UserSubscriptions.removeSubreddit( - subreddit.getDisplayName().toLowerCase(Locale.ENGLISH), MainActivity.this); - pager.setCurrentItem(pager.getCurrentItem() - 1); - restartTheme(); - } - } - - private void dismissProgressDialog() { if (d != null && d.isShowing()) { d.dismiss(); diff --git a/app/src/main/java/me/edgan/redditslide/Activities/MainPagerAdapterComment.java b/app/src/main/java/me/edgan/redditslide/Activities/MainPagerAdapterComment.java index 5c925343d..d79b44889 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/MainPagerAdapterComment.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/MainPagerAdapterComment.java @@ -47,8 +47,8 @@ public void onPageScrolled( } } } else { - if (mainActivity.mAsyncGetSubreddit != null) { - mainActivity.mAsyncGetSubreddit.cancel(true); + if (mainActivity.sidebarController != null) { + mainActivity.sidebarController.cancelAsyncGetSubredditTask(); } if (mainActivity.header.getTranslationY() == 0) { diff --git a/app/src/main/java/me/edgan/redditslide/Activities/SidebarActions.java b/app/src/main/java/me/edgan/redditslide/Activities/SidebarActions.java new file mode 100644 index 000000000..fcb09cfbd --- /dev/null +++ b/app/src/main/java/me/edgan/redditslide/Activities/SidebarActions.java @@ -0,0 +1,28 @@ +package me.edgan.redditslide.Activities; + +import java.util.Locale; +import me.edgan.redditslide.UserSubscriptions; +import net.dean.jraw.models.Subreddit; + +public class SidebarActions { + + private final MainActivity mainActivity; + public boolean currentlySubbed; + + public SidebarActions(MainActivity mainActivity) { + this.mainActivity = mainActivity; + } + + + void changeSubscription(Subreddit subreddit, boolean isChecked) { + currentlySubbed = isChecked; + if (isChecked) { + UserSubscriptions.addSubreddit(subreddit.getDisplayName().toLowerCase(Locale.ENGLISH), mainActivity); + } else { + UserSubscriptions.removeSubreddit(subreddit.getDisplayName().toLowerCase(Locale.ENGLISH), mainActivity); + + mainActivity.pager.setCurrentItem(mainActivity.pager.getCurrentItem() - 1); + mainActivity.restartTheme(); + } + } +} \ No newline at end of file diff --git a/app/src/main/java/me/edgan/redditslide/Activities/SidebarController.java b/app/src/main/java/me/edgan/redditslide/Activities/SidebarController.java index db0b6409f..a940bdfb1 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/SidebarController.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/SidebarController.java @@ -72,14 +72,20 @@ public class SidebarController { private Sorting sorts; private TimePeriod time = TimePeriod.DAY; private AsyncTask currentFlair; + private final SpoilerRobotoTextView sidebarBody; // Moved from MainActivity + private final CommentOverflow sidebarOverflow; // Moved from MainActivity + private AsyncGetSubredditTask mAsyncGetSubreddit = null; // Moved from MainActivity public SidebarController(MainActivity mainActivity) { this.mainActivity = mainActivity; + // Initialize views moved from MainActivity + this.sidebarBody = (SpoilerRobotoTextView) mainActivity.findViewById(R.id.sidebar_text); + this.sidebarOverflow = (CommentOverflow) mainActivity.findViewById(R.id.commentOverflow); } public void doSubSidebar(final String subreddit) { - if (mainActivity.mAsyncGetSubreddit != null) { - mainActivity.mAsyncGetSubreddit.cancel(true); + if (this.mAsyncGetSubreddit != null) { + this.mAsyncGetSubreddit.cancel(true); } mainActivity.findViewById(R.id.loader).setVisibility(View.VISIBLE); @@ -96,8 +102,8 @@ public void doSubSidebar(final String subreddit) { mainActivity.drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, GravityCompat.END); } - mainActivity.mAsyncGetSubreddit = new AsyncGetSubredditTask(mainActivity); - mainActivity.mAsyncGetSubreddit.execute(subreddit); + this.mAsyncGetSubreddit = new AsyncGetSubredditTask(this); + this.mAsyncGetSubreddit.execute(subreddit); final View dialoglayout = mainActivity.findViewById(R.id.sidebarsub); { @@ -570,8 +576,8 @@ public void onClick(DialogInterface dialogInterface, int i) { } public void doSubSidebarNoLoad(final String subreddit) { - if (mainActivity.mAsyncGetSubreddit != null) { - mainActivity.mAsyncGetSubreddit.cancel(true); + if (this.mAsyncGetSubreddit != null) { + this.mAsyncGetSubreddit.cancel(true); } mainActivity.findViewById(R.id.loader).setVisibility(View.GONE); @@ -589,7 +595,7 @@ public void doSubSidebarNoLoad(final String subreddit) { mainActivity.drawerLayout.setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, GravityCompat.END); } - mainActivity.findViewById(R.id.sidebar_text).setVisibility(View.GONE); + this.sidebarBody.setVisibility(View.GONE); mainActivity.findViewById(R.id.sub_title).setVisibility(View.GONE); mainActivity.findViewById(R.id.subscribers).setVisibility(View.GONE); mainActivity.findViewById(R.id.active_users).setVisibility(View.GONE); @@ -625,7 +631,7 @@ public void doSubOnlyStuff(final Subreddit subreddit) { mainActivity.findViewById(R.id.sidebar_text).setVisibility(View.VISIBLE); final String text = subreddit.getDataNode().get("description_html").asText().trim(); - setViews(text, subreddit.getDisplayName(), mainActivity.sidebarBody, mainActivity.sidebarOverflow); + setViews(text, subreddit.getDisplayName(), this.sidebarBody, this.sidebarOverflow); // get all subs that have Notifications enabled ArrayList rawSubs = StringUtil.stringToArray(Reddit.appRestart.getString(CheckForMail.SUBS_TO_GET, "")); @@ -643,7 +649,7 @@ public void doSubOnlyStuff(final Subreddit subreddit) { boolean isNotified =subThresholds.containsKey(subreddit.getDisplayName().toLowerCase(Locale.ENGLISH)); ((AppCompatCheckBox) mainActivity.findViewById(R.id.notify_posts_state)).setChecked(isNotified); } else { - mainActivity.findViewById(R.id.sidebar_text).setVisibility(View.GONE); + this.sidebarBody.setVisibility(View.GONE); } { View collection = mainActivity.findViewById(R.id.collection); @@ -827,11 +833,11 @@ public boolean onSelection(MaterialDialog dialog, View itemView, int which, Char } { final TextView subscribe = (TextView) mainActivity.findViewById(R.id.subscribe); - mainActivity.currentlySubbed = + mainActivity.sidebarActions.currentlySubbed = (!Authentication.isLoggedIn && mainActivity.usedArray.contains(subreddit.getDisplayName().toLowerCase(Locale.ENGLISH))) || subreddit.isUserSubscriber(); - MiscUtil.doSubscribeButtonText(mainActivity.currentlySubbed, subscribe); + MiscUtil.doSubscribeButtonText(mainActivity.sidebarActions.currentlySubbed, subscribe); assert subscribe != null; subscribe.setOnClickListener( @@ -853,7 +859,7 @@ public void onPostExecute( .setPositiveButton( R.string.btn_yes, (dialog1, which1) -> { - mainActivity.changeSubscription( subreddit, true); // Force add the subscription + mainActivity.sidebarActions.changeSubscription( subreddit, true); // Force add the subscription Snackbar s = Snackbar.make( mainActivity.mToolbar, mainActivity.getString(R.string.misc_subscribed), Snackbar.LENGTH_LONG @@ -864,7 +870,7 @@ public void onPostExecute( .setCancelable(false) .show(); } else { - mainActivity.changeSubscription( + mainActivity.sidebarActions.changeSubscription( subreddit, true); } } @@ -883,14 +889,14 @@ protected Boolean doInBackground( .setNeutralButton( R.string.btn_add_to_sublist, (dialog, which) -> { - mainActivity.changeSubscription(subreddit, true); // Force add the subscription + mainActivity.sidebarActions.changeSubscription(subreddit, true); // Force add the subscription Snackbar s = Snackbar.make(mainActivity.mToolbar, R.string.sub_added, Snackbar.LENGTH_LONG); LayoutUtils.showSnackbar(s); }) .setNegativeButton(R.string.btn_cancel, null) .show(); } else { - mainActivity.changeSubscription(subreddit, true); + mainActivity.sidebarActions.changeSubscription(subreddit, true); } } @@ -909,7 +915,7 @@ public void onPostExecute(Boolean success) { .setMessage(R.string.force_change_subscription_desc) .setPositiveButton(R.string.btn_yes, (dialog12, which12) -> { - mainActivity.changeSubscription(subreddit, false); // Force add the subscription + mainActivity.sidebarActions.changeSubscription(subreddit, false); // Force add the subscription Snackbar s = Snackbar.make( mainActivity.mToolbar, mainActivity.getString(R.string.misc_unsubscribed), @@ -922,7 +928,7 @@ public void onPostExecute(Boolean success) { .setCancelable(false) .show(); } else { - mainActivity.changeSubscription(subreddit, false); + mainActivity.sidebarActions.changeSubscription(subreddit, false); } } @@ -939,7 +945,7 @@ protected Boolean doInBackground( }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR)) .setNeutralButton( R.string.just_unsub, - (dialog, which) -> { mainActivity.changeSubscription(subreddit, false); // Force add the subscription + (dialog, which) -> { mainActivity.sidebarActions.changeSubscription(subreddit, false); // Force add the subscription Snackbar s = Snackbar.make(mainActivity.mToolbar, R.string.misc_unsubscribed, Snackbar.LENGTH_LONG); LayoutUtils.showSnackbar(s); } @@ -947,18 +953,18 @@ protected Boolean doInBackground( .setNegativeButton(R.string.btn_cancel, null) .show(); } else { - mainActivity.changeSubscription(subreddit, false); + mainActivity.sidebarActions.changeSubscription(subreddit, false); } } @Override public void onClick(View v) { - if (!mainActivity.currentlySubbed) { + if (!mainActivity.sidebarActions.currentlySubbed) { doSubscribe(); } else { doUnsubscribe(); } - MiscUtil.doSubscribeButtonText(mainActivity.currentlySubbed, subscribe); + MiscUtil.doSubscribeButtonText(mainActivity.sidebarActions.currentlySubbed, subscribe); } }); } @@ -967,8 +973,8 @@ public void onClick(View v) { setViews( subreddit.getDataNode().get("public_description_html").asText(), subreddit.getDisplayName().toLowerCase(Locale.ENGLISH), - ((SpoilerRobotoTextView) mainActivity.findViewById(R.id.sub_title)), - (CommentOverflow) mainActivity.findViewById(R.id.sub_title_overflow) + ((SpoilerRobotoTextView) mainActivity.findViewById(R.id.sub_title)), // Keep using findViewById for views not moved + (CommentOverflow) mainActivity.findViewById(R.id.sub_title_overflow) // Keep using findViewById for views not moved ); } else { mainActivity.findViewById(R.id.sub_title).setVisibility(View.GONE); @@ -1038,9 +1044,28 @@ private void setViews( if (maybeScrollable instanceof HorizontalScrollView) { sidebar.addScrollable(maybeScrollable); } + } } else { commentOverflow.removeAllViews(); } } + + /** + * Helper method to check if the associated MainActivity is still valid. + * Used by AsyncGetSubredditTask to avoid crashes if the activity is destroyed. + * @return true if the activity is not null and not destroyed, false otherwise. + */ + public boolean isActivityValid() { + return mainActivity != null && !mainActivity.isDestroyed(); + } + + /** + * Cancels the currently running AsyncGetSubredditTask, if any. + */ + public void cancelAsyncGetSubredditTask() { + if (mAsyncGetSubreddit != null) { + mAsyncGetSubreddit.cancel(true); + } + } } \ No newline at end of file From fd0c1348e13cc5f775cea8f82cdbf2a60b05f092 Mon Sep 17 00:00:00 2001 From: Nathan Grennan Date: Mon, 31 Mar 2025 00:35:26 -0700 Subject: [PATCH 09/99] Broke out the SubredditSortController class from MainActivity.java --- .../redditslide/Activities/MainActivity.java | 230 +--------------- .../Activities/SubredditSortController.java | 253 ++++++++++++++++++ 2 files changed, 256 insertions(+), 227 deletions(-) create mode 100644 app/src/main/java/me/edgan/redditslide/Activities/SubredditSortController.java diff --git a/app/src/main/java/me/edgan/redditslide/Activities/MainActivity.java b/app/src/main/java/me/edgan/redditslide/Activities/MainActivity.java index f499685ec..57bc19265 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/MainActivity.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/MainActivity.java @@ -22,12 +22,10 @@ import android.os.Handler; import android.text.Editable; import android.text.InputType; -import android.text.Spannable; import android.text.TextWatcher; import android.util.Log; import android.util.Pair; import android.util.TypedValue; -import android.view.Gravity; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.Menu; @@ -49,7 +47,6 @@ import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.view.ContextThemeWrapper; -import androidx.appcompat.widget.PopupMenu; import androidx.appcompat.widget.SwitchCompat; import androidx.appcompat.widget.Toolbar; import androidx.core.content.pm.ShortcutInfoCompat; @@ -103,16 +100,13 @@ import me.edgan.redditslide.util.NetworkStateReceiver; import me.edgan.redditslide.util.NetworkUtil; import me.edgan.redditslide.util.OnSingleClickListener; -import me.edgan.redditslide.util.SortingUtil; import me.edgan.redditslide.util.TimeUtils; import me.edgan.redditslide.util.FilterContentUtil; import net.dean.jraw.managers.AccountManager; import net.dean.jraw.models.MultiReddit; import net.dean.jraw.models.Submission; -import net.dean.jraw.paginators.Sorting; import net.dean.jraw.paginators.SubredditPaginator; -import net.dean.jraw.paginators.TimePeriod; import java.lang.reflect.Field; import java.util.ArrayList; @@ -191,6 +185,7 @@ public class MainActivity extends BaseActivity public ToolbarSearchController toolbarSearchController; SidebarController sidebarController; SidebarActions sidebarActions; + SubredditSortController subredditSortController; @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == SETTINGS_RESULT) { @@ -553,7 +548,7 @@ public void onClick(View v) { Snackbar.LENGTH_SHORT); LayoutUtils.showSnackbar(s); } else { - openPopup(); + subredditSortController.openPopup(); } return true; case R.id.search: @@ -934,6 +929,7 @@ public void onSingleClick( toolbarSearchController = new ToolbarSearchController(this); sidebarController = new SidebarController(this); // Added initialization sidebarActions = new SidebarActions(this); + subredditSortController = new SubredditSortController(this); rootView = findViewById(android.R.id.content); @@ -1359,64 +1355,8 @@ public void doPageSelectedComments(int position) { } - Sorting sorts; - TimePeriod time = TimePeriod.DAY; - - private void askTimePeriod(final Sorting sort, final String sub, final View dialoglayout) { - final DialogInterface.OnClickListener l2 = - new DialogInterface.OnClickListener() { - - @Override - public void onClick(DialogInterface dialogInterface, int i) { - switch (i) { - case 0: - time = TimePeriod.HOUR; - break; - case 1: - time = TimePeriod.DAY; - break; - case 2: - time = TimePeriod.WEEK; - break; - case 3: - time = TimePeriod.MONTH; - break; - case 4: - time = TimePeriod.YEAR; - break; - case 5: - time = TimePeriod.ALL; - break; - } - SettingValues.setSubSorting(sort, time, sub); - SortingUtil.setSorting(sub, sort); - SortingUtil.setTime(sub, time); - final TextView sort = dialoglayout.findViewById(R.id.sort); - if (SettingValues.hasSort(sub)) { - Sorting sortingis = SettingValues.getBaseSubmissionSort(sub); - sort.setText( - sortingis.name() - + ((sortingis == Sorting.CONTROVERSIAL - || sortingis == Sorting.TOP) - ? " of " - + SettingValues.getBaseTimePeriod(sub) - .name() - : "")); - } else { - sort.setText("Set default sorting"); - } - reloadSubs(); - } - }; - new AlertDialog.Builder(MainActivity.this) - .setTitle(R.string.sorting_choose) - .setSingleChoiceItems( - SortingUtil.getSortingTimesStrings(), SortingUtil.getSortingTimeId(""), l2) - .show(); - } - /** * Starts the enter animations for various UI components of the toolbar subreddit search @@ -1473,171 +1413,7 @@ public int getCurrentPage() { return position; } - public void openPopup() { - PopupMenu popup = - new PopupMenu(MainActivity.this, findViewById(R.id.anchor), Gravity.RIGHT); - String id = - ((SubmissionsView) (((MainPagerAdapter) pager.getAdapter()).getCurrentFragment())) - .id; - - final Spannable[] base = SortingUtil.getSortingSpannables(id); - for (Spannable s : base) { - // Do not add option for "Best" in any subreddit except for the frontpage. - if (!id.equals("frontpage") && s.toString().equals(getString(R.string.sorting_best))) { - continue; - } - MenuItem m = popup.getMenu().add(s); - } - popup.setOnMenuItemClickListener( - new PopupMenu.OnMenuItemClickListener() { - public boolean onMenuItemClick(MenuItem item) { - int i = 0; - for (Spannable s : base) { - if (s.equals(item.getTitle())) { - break; - } - i++; - } - - LogUtil.v("Chosen is " + i); - switch (i) { - case 0: - if (id.equals("frontpage")) { - SortingUtil.frontpageSorting = Sorting.HOT; - } else { - SortingUtil.setSorting(id, Sorting.HOT); - } - reloadSubs(); - break; - case 1: - if (id.equals("frontpage")) { - SortingUtil.frontpageSorting = Sorting.NEW; - } else { - SortingUtil.setSorting(id, Sorting.NEW); - } - reloadSubs(); - break; - case 2: - if (id.equals("frontpage")) { - SortingUtil.frontpageSorting = Sorting.RISING; - } else { - SortingUtil.setSorting(id, Sorting.RISING); - } - reloadSubs(); - break; - case 3: - if (id.equals("frontpage")) { - SortingUtil.frontpageSorting = Sorting.TOP; - } else { - SortingUtil.setSorting(id, Sorting.TOP); - } - openPopupTime(); - break; - case 4: - if (id.equals("frontpage")) { - SortingUtil.frontpageSorting = Sorting.CONTROVERSIAL; - } else { - SortingUtil.setSorting(id, Sorting.CONTROVERSIAL); - } - openPopupTime(); - break; - case 5: - if (id.equals("frontpage")) { - SortingUtil.frontpageSorting = Sorting.BEST; - } else { - SortingUtil.setSorting(id, Sorting.BEST); - } - reloadSubs(); - break; - } - return true; - } - }); - popup.show(); - } - public void openPopupTime() { - PopupMenu popup = - new PopupMenu(MainActivity.this, findViewById(R.id.anchor), Gravity.RIGHT); - String id = - ((SubmissionsView) (((MainPagerAdapter) pager.getAdapter()).getCurrentFragment())) - .id; - final Spannable[] base = SortingUtil.getSortingTimesSpannables(id); - for (Spannable s : base) { - MenuItem m = popup.getMenu().add(s); - } - popup.setOnMenuItemClickListener( - new PopupMenu.OnMenuItemClickListener() { - public boolean onMenuItemClick(MenuItem item) { - LogUtil.v("Chosen is " + item.getOrder()); - int i = 0; - for (Spannable s : base) { - if (s.equals(item.getTitle())) { - break; - } - i++; - } - switch (i) { - case 0: - SortingUtil.setTime( - ((SubmissionsView) - (((MainPagerAdapter) pager.getAdapter()) - .getCurrentFragment())) - .id, - TimePeriod.HOUR); - reloadSubs(); - break; - case 1: - SortingUtil.setTime( - ((SubmissionsView) - (((MainPagerAdapter) pager.getAdapter()) - .getCurrentFragment())) - .id, - TimePeriod.DAY); - reloadSubs(); - break; - case 2: - SortingUtil.setTime( - ((SubmissionsView) - (((MainPagerAdapter) pager.getAdapter()) - .getCurrentFragment())) - .id, - TimePeriod.WEEK); - reloadSubs(); - break; - case 3: - SortingUtil.setTime( - ((SubmissionsView) - (((MainPagerAdapter) pager.getAdapter()) - .getCurrentFragment())) - .id, - TimePeriod.MONTH); - reloadSubs(); - break; - case 4: - SortingUtil.setTime( - ((SubmissionsView) - (((MainPagerAdapter) pager.getAdapter()) - .getCurrentFragment())) - .id, - TimePeriod.YEAR); - reloadSubs(); - break; - case 5: - SortingUtil.setTime( - ((SubmissionsView) - (((MainPagerAdapter) pager.getAdapter()) - .getCurrentFragment())) - .id, - TimePeriod.ALL); - reloadSubs(); - break; - } - return true; - } - }); - popup.show(); - } public static String randomoverride; diff --git a/app/src/main/java/me/edgan/redditslide/Activities/SubredditSortController.java b/app/src/main/java/me/edgan/redditslide/Activities/SubredditSortController.java new file mode 100644 index 000000000..2d5626d0f --- /dev/null +++ b/app/src/main/java/me/edgan/redditslide/Activities/SubredditSortController.java @@ -0,0 +1,253 @@ +package me.edgan.redditslide.Activities; + +import android.content.DialogInterface; +import android.text.Spannable; +import android.view.Gravity; +import android.view.MenuItem; +import android.view.View; +import android.widget.TextView; +import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.widget.PopupMenu; + +import me.edgan.redditslide.Fragments.SubmissionsView; +import me.edgan.redditslide.R; +import me.edgan.redditslide.SettingValues; +import me.edgan.redditslide.util.LogUtil; +import me.edgan.redditslide.util.SortingUtil; +import net.dean.jraw.paginators.Sorting; +import net.dean.jraw.paginators.TimePeriod; + +/** + * Handles the sorting selection logic for subreddits within MainActivity. + */ +public class SubredditSortController { + + private final MainActivity activity; + + Sorting sorts; + TimePeriod time = TimePeriod.DAY; + + + public SubredditSortController(MainActivity activity) { + this.activity = activity; + } + + public void openPopup() { + PopupMenu popup = + new PopupMenu(activity, activity.findViewById(R.id.anchor), Gravity.RIGHT); + String id = + ((SubmissionsView) (((MainPagerAdapter) activity.pager.getAdapter()).getCurrentFragment())) + .id; + + final Spannable[] base = SortingUtil.getSortingSpannables(id); + for (Spannable s : base) { + // Do not add option for "Best" in any subreddit except for the frontpage. + if (!id.equals("frontpage") && s.toString().equals(activity.getString(R.string.sorting_best))) { + continue; + } + MenuItem m = popup.getMenu().add(s); + } + popup.setOnMenuItemClickListener( + new PopupMenu.OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + int i = 0; + for (Spannable s : base) { + if (s.equals(item.getTitle())) { + break; + } + i++; + } + + LogUtil.v("Chosen is " + i); + switch (i) { + case 0: + if (id.equals("frontpage")) { + SortingUtil.frontpageSorting = Sorting.HOT; + } else { + SortingUtil.setSorting(id, Sorting.HOT); + } + activity.reloadSubs(); + break; + case 1: + if (id.equals("frontpage")) { + SortingUtil.frontpageSorting = Sorting.NEW; + } else { + SortingUtil.setSorting(id, Sorting.NEW); + } + activity.reloadSubs(); + break; + case 2: + if (id.equals("frontpage")) { + SortingUtil.frontpageSorting = Sorting.RISING; + } else { + SortingUtil.setSorting(id, Sorting.RISING); + } + activity.reloadSubs(); + break; + case 3: + if (id.equals("frontpage")) { + SortingUtil.frontpageSorting = Sorting.TOP; + } else { + SortingUtil.setSorting(id, Sorting.TOP); + } + openPopupTime(); + break; + case 4: + if (id.equals("frontpage")) { + SortingUtil.frontpageSorting = Sorting.CONTROVERSIAL; + } else { + SortingUtil.setSorting(id, Sorting.CONTROVERSIAL); + } + openPopupTime(); + break; + case 5: + if (id.equals("frontpage")) { + SortingUtil.frontpageSorting = Sorting.BEST; + } else { + SortingUtil.setSorting(id, Sorting.BEST); + } + activity.reloadSubs(); + break; + } + return true; + } + }); + popup.show(); + } + + public void openPopupTime() { + PopupMenu popup = + new PopupMenu(activity, activity.findViewById(R.id.anchor), Gravity.RIGHT); + String id = + ((SubmissionsView) (((MainPagerAdapter) activity.pager.getAdapter()).getCurrentFragment())) + .id; + final Spannable[] base = SortingUtil.getSortingTimesSpannables(id); + for (Spannable s : base) { + MenuItem m = popup.getMenu().add(s); + } + popup.setOnMenuItemClickListener( + new PopupMenu.OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + LogUtil.v("Chosen is " + item.getOrder()); + int i = 0; + for (Spannable s : base) { + if (s.equals(item.getTitle())) { + break; + } + i++; + } + switch (i) { + case 0: + SortingUtil.setTime( + ((SubmissionsView) + (((MainPagerAdapter) activity.pager.getAdapter()) + .getCurrentFragment())) + .id, + TimePeriod.HOUR); + activity.reloadSubs(); + break; + case 1: + SortingUtil.setTime( + ((SubmissionsView) + (((MainPagerAdapter) activity.pager.getAdapter()) + .getCurrentFragment())) + .id, + TimePeriod.DAY); + activity.reloadSubs(); + break; + case 2: + SortingUtil.setTime( + ((SubmissionsView) + (((MainPagerAdapter) activity.pager.getAdapter()) + .getCurrentFragment())) + .id, + TimePeriod.WEEK); + activity.reloadSubs(); + break; + case 3: + SortingUtil.setTime( + ((SubmissionsView) + (((MainPagerAdapter) activity.pager.getAdapter()) + .getCurrentFragment())) + .id, + TimePeriod.MONTH); + activity.reloadSubs(); + break; + case 4: + SortingUtil.setTime( + ((SubmissionsView) + (((MainPagerAdapter) activity.pager.getAdapter()) + .getCurrentFragment())) + .id, + TimePeriod.YEAR); + activity.reloadSubs(); + break; + case 5: + SortingUtil.setTime( + ((SubmissionsView) + (((MainPagerAdapter) activity.pager.getAdapter()) + .getCurrentFragment())) + .id, + TimePeriod.ALL); + activity.reloadSubs(); + break; + } + return true; + } + }); + popup.show(); + } + + public void askTimePeriod(final Sorting sort, final String sub, final View dialoglayout) { + final DialogInterface.OnClickListener l2 = + new DialogInterface.OnClickListener() { + + @Override + public void onClick(DialogInterface dialogInterface, int i) { + switch (i) { + case 0: + time = TimePeriod.HOUR; + break; + case 1: + time = TimePeriod.DAY; + break; + case 2: + time = TimePeriod.WEEK; + break; + case 3: + time = TimePeriod.MONTH; + break; + case 4: + time = TimePeriod.YEAR; + break; + case 5: + time = TimePeriod.ALL; + break; + } + SettingValues.setSubSorting(sort, time, sub); + SortingUtil.setSorting(sub, sort); + SortingUtil.setTime(sub, time); + final TextView sortTextView = dialoglayout.findViewById(R.id.sort); + if (SettingValues.hasSort(sub)) { + Sorting sortingis = SettingValues.getBaseSubmissionSort(sub); + sortTextView.setText( + sortingis.name() + + ((sortingis == Sorting.CONTROVERSIAL + || sortingis == Sorting.TOP) + ? " of " + + SettingValues.getBaseTimePeriod(sub) + .name() + : "")); + } else { + sortTextView.setText("Set default sorting"); + } + activity.reloadSubs(); + } + }; + new AlertDialog.Builder(activity) // Use activity context + .setTitle(R.string.sorting_choose) + .setSingleChoiceItems( + SortingUtil.getSortingTimesStrings(), SortingUtil.getSortingTimeId(""), l2) + .show(); + } +} \ No newline at end of file From 912c054da67058168177d7f45e4fc2ed288d9a0b Mon Sep 17 00:00:00 2001 From: Nathan Grennan Date: Mon, 31 Mar 2025 16:55:32 -0700 Subject: [PATCH 10/99] Broke functions out of PopulateSubmissionViewHolder.java into SubmissionThumbnailHelper.java --- .../redditslide/Adapters/GalleryView.java | 8 +- .../redditslide/Fragments/CommentPage.java | 8 +- .../redditslide/Fragments/MediaFragment.java | 8 +- .../PopulateSubmissionViewHolder.java | 231 +----------------- .../util/SubmissionThumbnailHelper.java | 165 +++++++++++++ 5 files changed, 181 insertions(+), 239 deletions(-) create mode 100644 app/src/main/java/me/edgan/redditslide/util/SubmissionThumbnailHelper.java diff --git a/app/src/main/java/me/edgan/redditslide/Adapters/GalleryView.java b/app/src/main/java/me/edgan/redditslide/Adapters/GalleryView.java index 825879f42..4e53e3167 100644 --- a/app/src/main/java/me/edgan/redditslide/Adapters/GalleryView.java +++ b/app/src/main/java/me/edgan/redditslide/Adapters/GalleryView.java @@ -36,7 +36,7 @@ import me.edgan.redditslide.R; import me.edgan.redditslide.Reddit; import me.edgan.redditslide.SettingValues; -import me.edgan.redditslide.SubmissionViews.PopulateSubmissionViewHolder; +import me.edgan.redditslide.util.SubmissionThumbnailHelper; import me.edgan.redditslide.Visuals.Palette; import me.edgan.redditslide.util.BlendModeUtil; import me.edgan.redditslide.util.CompatUtil; @@ -265,7 +265,7 @@ public void onClick(View v) { case DEVIANTART: case XKCD: case IMAGE: - PopulateSubmissionViewHolder.openImage( + SubmissionThumbnailHelper.openImage( type, main, submission, @@ -292,7 +292,7 @@ public void onClick(View v) { } break; case REDDIT: - PopulateSubmissionViewHolder.openRedditContent( + SubmissionThumbnailHelper.openRedditContent( submission.getUrl(), main); break; case LINK: @@ -380,7 +380,7 @@ public void onClick(View v) { } break; case GIF: - PopulateSubmissionViewHolder.openGif( + SubmissionThumbnailHelper.openGif( main, submission, holder.getBindingAdapterPosition()); diff --git a/app/src/main/java/me/edgan/redditslide/Fragments/CommentPage.java b/app/src/main/java/me/edgan/redditslide/Fragments/CommentPage.java index 04c243e94..0dba09a9c 100644 --- a/app/src/main/java/me/edgan/redditslide/Fragments/CommentPage.java +++ b/app/src/main/java/me/edgan/redditslide/Fragments/CommentPage.java @@ -83,7 +83,7 @@ import me.edgan.redditslide.Reddit; import me.edgan.redditslide.SettingValues; import me.edgan.redditslide.SpoilerRobotoTextView; -import me.edgan.redditslide.SubmissionViews.PopulateSubmissionViewHolder; +import me.edgan.redditslide.util.SubmissionThumbnailHelper; import me.edgan.redditslide.UserSubscriptions; import me.edgan.redditslide.Views.CommentOverflow; import me.edgan.redditslide.Views.DoEditorActions; @@ -1084,7 +1084,7 @@ public boolean onMenuItemClick(MenuItem item) { } break; case REDDIT: - PopulateSubmissionViewHolder.openRedditContent( + SubmissionThumbnailHelper.openRedditContent( adapter.submission.getUrl(), getActivity()); break; case LINK: @@ -1170,13 +1170,13 @@ public boolean onMenuItemClick(MenuItem item) { } break; case IMAGE: - PopulateSubmissionViewHolder.openImage( + SubmissionThumbnailHelper.openImage( type, getActivity(), adapter.submission, null, -1); break; case VREDDIT_REDIRECT: case VREDDIT_DIRECT: case GIF: - PopulateSubmissionViewHolder.openGif( + SubmissionThumbnailHelper.openGif( getActivity(), adapter.submission, -1); break; case VIDEO: diff --git a/app/src/main/java/me/edgan/redditslide/Fragments/MediaFragment.java b/app/src/main/java/me/edgan/redditslide/Fragments/MediaFragment.java index 5eabe5d1a..7ba3c81e5 100644 --- a/app/src/main/java/me/edgan/redditslide/Fragments/MediaFragment.java +++ b/app/src/main/java/me/edgan/redditslide/Fragments/MediaFragment.java @@ -51,7 +51,7 @@ import me.edgan.redditslide.SecretConstants; import me.edgan.redditslide.SettingValues; import me.edgan.redditslide.SubmissionViews.PopulateShadowboxInfo; -import me.edgan.redditslide.SubmissionViews.PopulateSubmissionViewHolder; +import me.edgan.redditslide.util.SubmissionThumbnailHelper; import me.edgan.redditslide.Views.ExoVideoView; import me.edgan.redditslide.Views.ImageSource; import me.edgan.redditslide.Views.SubsamplingScaleImageView; @@ -383,7 +383,7 @@ public void onClick(View v) { } break; case REDDIT: - PopulateSubmissionViewHolder.openRedditContent( + SubmissionThumbnailHelper.openRedditContent( submission.getUrl(), contextActivity); break; @@ -482,11 +482,11 @@ public void onClick(View v) { case DEVIANTART: case XKCD: case IMAGE: - PopulateSubmissionViewHolder.openImage( + SubmissionThumbnailHelper.openImage( type, contextActivity, submission, null, -1); break; case GIF: - PopulateSubmissionViewHolder.openGif( + SubmissionThumbnailHelper.openGif( contextActivity, submission, -1); break; case VIDEO: diff --git a/app/src/main/java/me/edgan/redditslide/SubmissionViews/PopulateSubmissionViewHolder.java b/app/src/main/java/me/edgan/redditslide/SubmissionViews/PopulateSubmissionViewHolder.java index f2744549a..87a44101e 100644 --- a/app/src/main/java/me/edgan/redditslide/SubmissionViews/PopulateSubmissionViewHolder.java +++ b/app/src/main/java/me/edgan/redditslide/SubmissionViews/PopulateSubmissionViewHolder.java @@ -66,7 +66,6 @@ import me.edgan.redditslide.Authentication; import me.edgan.redditslide.CommentCacheAsync; import me.edgan.redditslide.ContentType; -import me.edgan.redditslide.DataShare; import me.edgan.redditslide.ForceTouch.PeekViewActivity; import me.edgan.redditslide.HasSeen; import me.edgan.redditslide.Hidden; @@ -90,13 +89,13 @@ import me.edgan.redditslide.util.ClipboardUtil; import me.edgan.redditslide.util.CompatUtil; import me.edgan.redditslide.util.DisplayUtil; -import me.edgan.redditslide.util.GifUtils; import me.edgan.redditslide.util.JsonUtil; import me.edgan.redditslide.util.LayoutUtils; import me.edgan.redditslide.util.LinkUtil; import me.edgan.redditslide.util.NetworkUtil; import me.edgan.redditslide.util.OnSingleClickListener; import me.edgan.redditslide.util.SubmissionParser; +import me.edgan.redditslide.util.SubmissionThumbnailHelper; import net.dean.jraw.ApiException; import net.dean.jraw.fluent.FlairReference; @@ -188,7 +187,7 @@ public void onSingleClick(View v) { case DEVIANTART: case XKCD: case IMAGE: - openImage( + SubmissionThumbnailHelper.openImage( type, contextActivity, submission, @@ -218,7 +217,7 @@ public void onSingleClick(View v) { } break; case REDDIT: - openRedditContent(submission.getUrl(), contextActivity); + SubmissionThumbnailHelper.openRedditContent(submission.getUrl(), contextActivity); break; case REDDIT_GALLERY: if (SettingValues.album) { @@ -363,7 +362,7 @@ public void onSingleClick(View v) { case VREDDIT_REDIRECT: case GIF: case VREDDIT_DIRECT: - openGif( + SubmissionThumbnailHelper.openGif( contextActivity, submission, holder.getBindingAdapterPosition()); @@ -403,228 +402,6 @@ public void onSingleClick(View v) { }); } - public static void openRedditContent(String url, Context c) { - OpenRedditLink.openUrl(c, url, true); - } - - public static void openImage( - ContentType.Type type, - Activity contextActivity, - Submission submission, - HeaderImageLinkView baseView, - int adapterPosition) { - if (SettingValues.image) { - Intent myIntent = new Intent(contextActivity, MediaView.class); - myIntent.putExtra(MediaView.SUBREDDIT, submission.getSubredditName()); - myIntent.putExtra(EXTRA_SUBMISSION_TITLE, submission.getTitle()); - String previewUrl; - String url = submission.getUrl(); - - if (baseView != null - && baseView.lq - && SettingValues.loadImageLq - && type != ContentType.Type.XKCD) { - myIntent.putExtra(MediaView.EXTRA_LQ, true); - myIntent.putExtra(MediaView.EXTRA_DISPLAY_URL, baseView.loadedUrl); - } else if (submission.getDataNode().has("preview") - && submission - .getDataNode() - .get("preview") - .get("images") - .get(0) - .get("source") - .has("height") - && type - != ContentType.Type - .XKCD) { // Load the preview image which has probably already - // been cached in memory instead of the direct link - previewUrl = - submission - .getDataNode() - .get("preview") - .get("images") - .get(0) - .get("source") - .get("url") - .asText(); - if (baseView == null || (!SettingValues.loadImageLq && baseView.lq)) { - myIntent.putExtra(MediaView.EXTRA_DISPLAY_URL, previewUrl); - } else { - myIntent.putExtra(MediaView.EXTRA_DISPLAY_URL, baseView.loadedUrl); - } - } - myIntent.putExtra(MediaView.EXTRA_URL, url); - PopulateBase.addAdaptorPosition(myIntent, submission, adapterPosition); - myIntent.putExtra(MediaView.EXTRA_SHARE_URL, submission.getUrl()); - - contextActivity.startActivity(myIntent); - - } else { - LinkUtil.openExternally(submission.getUrl()); - } - } - - public static void openGif( - Activity contextActivity, Submission submission, int adapterPosition) { - if (SettingValues.gif) { - DataShare.sharedSubmission = submission; - - Intent myIntent = new Intent(contextActivity, MediaView.class); - myIntent.putExtra(MediaView.SUBREDDIT, submission.getSubredditName()); - myIntent.putExtra(EXTRA_SUBMISSION_TITLE, submission.getTitle()); - - GifUtils.AsyncLoadGif.VideoType t = - GifUtils.AsyncLoadGif.getVideoType(submission.getUrl()); - - if (t == GifUtils.AsyncLoadGif.VideoType.VREDDIT) { - if (submission.getDataNode().has("media") - && submission.getDataNode().get("media").has("reddit_video") - && submission - .getDataNode() - .get("media") - .get("reddit_video") - .has("hls_url")) { - myIntent.putExtra( - MediaView.EXTRA_URL, - StringEscapeUtils.unescapeJson( - submission - .getDataNode() - .get("media") - .get("reddit_video") - .get("dash_url") // In the future, we could - // load the HLS url as well - .asText()) - .replace("&", "&")); - } else if (submission.getDataNode().has("media") - && submission.getDataNode().get("media").has("reddit_video")) { - myIntent.putExtra( - MediaView.EXTRA_URL, - StringEscapeUtils.unescapeJson( - submission - .getDataNode() - .get("media") - .get("reddit_video") - .get("fallback_url") - .asText()) - .replace("&", "&")); - } else if (submission.getDataNode().has("crosspost_parent_list")) { - myIntent.putExtra( - MediaView.EXTRA_URL, - StringEscapeUtils.unescapeJson( - submission - .getDataNode() - .get("crosspost_parent_list") - .get(0) - .get("media") - .get("reddit_video") - .get("dash_url") - .asText()) - .replace("&", "&")); - } else { - new OpenVRedditTask(contextActivity, submission.getSubredditName()) - .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, submission.getUrl()); - return; - } - - } else if (t.shouldLoadPreview() - && submission.getDataNode().has("preview") - && submission.getDataNode().get("preview").get("images").get(0).has("variants") - && submission - .getDataNode() - .get("preview") - .get("images") - .get(0) - .get("variants") - .has("mp4")) { - myIntent.putExtra( - MediaView.EXTRA_URL, - StringEscapeUtils.unescapeJson( - submission - .getDataNode() - .get("preview") - .get("images") - .get(0) - .get("variants") - .get("mp4") - .get("source") - .get("url") - .asText()) - .replace("&", "&")); - } else if (t.shouldLoadPreview() - && submission.getDataNode().has("preview") - && submission - .getDataNode() - .get("preview") - .get("reddit_video_preview") - .has("fallback_url")) { - myIntent.putExtra( - MediaView.EXTRA_URL, - StringEscapeUtils.unescapeJson( - submission - .getDataNode() - .get("preview") - .get("reddit_video_preview") - .get("fallback_url") - .asText()) - .replace("&", "&")); - } else if (t == GifUtils.AsyncLoadGif.VideoType.DIRECT - && submission.getDataNode().has("media") - && submission.getDataNode().get("media").has("reddit_video") - && submission - .getDataNode() - .get("media") - .get("reddit_video") - .has("fallback_url")) { - myIntent.putExtra( - MediaView.EXTRA_URL, - StringEscapeUtils.unescapeJson( - submission - .getDataNode() - .get("media") - .get("reddit_video") - .get("fallback_url") - .asText()) - .replace("&", "&")); - - } else if (t != GifUtils.AsyncLoadGif.VideoType.OTHER) { - myIntent.putExtra(MediaView.EXTRA_URL, submission.getUrl()); - } else { - LinkUtil.openUrl( - submission.getUrl(), - Palette.getColor(submission.getSubredditName()), - contextActivity, - adapterPosition, - submission); - return; - } - if (submission.getDataNode().has("preview") - && submission - .getDataNode() - .get("preview") - .get("images") - .get(0) - .get("source") - .has("height")) { // Load the preview image which has probably - // already been cached in memory instead of the - // direct link - String previewUrl = - submission - .getDataNode() - .get("preview") - .get("images") - .get(0) - .get("source") - .get("url") - .asText(); - myIntent.putExtra(MediaView.EXTRA_DISPLAY_URL, previewUrl); - } - PopulateBase.addAdaptorPosition(myIntent, submission, adapterPosition); - contextActivity.startActivity(myIntent); - } else { - LinkUtil.openExternally(submission.getUrl()); - } - } - public String reason; boolean[] chosen = new boolean[] {false, false, false}; diff --git a/app/src/main/java/me/edgan/redditslide/util/SubmissionThumbnailHelper.java b/app/src/main/java/me/edgan/redditslide/util/SubmissionThumbnailHelper.java new file mode 100644 index 000000000..a35dca375 --- /dev/null +++ b/app/src/main/java/me/edgan/redditslide/util/SubmissionThumbnailHelper.java @@ -0,0 +1,165 @@ +package me.edgan.redditslide.util; + +import static me.edgan.redditslide.Notifications.ImageDownloadNotificationService.EXTRA_SUBMISSION_TITLE; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.os.AsyncTask; + + +import me.edgan.redditslide.Activities.MediaView; +import me.edgan.redditslide.ContentType; +import me.edgan.redditslide.DataShare; +import me.edgan.redditslide.OpenRedditLink; +import me.edgan.redditslide.SettingValues; +import me.edgan.redditslide.SubmissionViews.HeaderImageLinkView; +import me.edgan.redditslide.SubmissionViews.OpenVRedditTask; +import me.edgan.redditslide.SubmissionViews.PopulateBase; +import me.edgan.redditslide.Visuals.Palette; + +import net.dean.jraw.models.Submission; + +import org.apache.commons.text.StringEscapeUtils; + + +public class SubmissionThumbnailHelper { + + public static void openRedditContent(String url, Context c) { + OpenRedditLink.openUrl(c, url, true); + } + + public static void openImage( + ContentType.Type type, + Activity contextActivity, + Submission submission, + HeaderImageLinkView baseView, + int adapterPosition) { + if (SettingValues.image) { + Intent myIntent = new Intent(contextActivity, MediaView.class); + myIntent.putExtra(MediaView.SUBREDDIT, submission.getSubredditName()); + myIntent.putExtra(EXTRA_SUBMISSION_TITLE, submission.getTitle()); + String previewUrl; + String url = submission.getUrl(); + + if (baseView != null && baseView.lq && SettingValues.loadImageLq && type != ContentType.Type.XKCD) { + myIntent.putExtra(MediaView.EXTRA_LQ, true); + myIntent.putExtra(MediaView.EXTRA_DISPLAY_URL, baseView.loadedUrl); + } else if (submission.getDataNode().has("preview") + && submission.getDataNode().get("preview").get("images").get(0).get("source").has("height") + && type != ContentType.Type.XKCD) { // Load the preview image which has probably already been cached in memory instead of the direct link + previewUrl = submission.getDataNode().get("preview").get("images").get(0).get("source").get("url").asText(); + + if (baseView == null || (!SettingValues.loadImageLq && baseView.lq)) { + myIntent.putExtra(MediaView.EXTRA_DISPLAY_URL, previewUrl); + } else { + myIntent.putExtra(MediaView.EXTRA_DISPLAY_URL, baseView.loadedUrl); + } + } + myIntent.putExtra(MediaView.EXTRA_URL, url); + PopulateBase.addAdaptorPosition(myIntent, submission, adapterPosition); + myIntent.putExtra(MediaView.EXTRA_SHARE_URL, submission.getUrl()); + + contextActivity.startActivity(myIntent); + + } else { + LinkUtil.openExternally(submission.getUrl()); + } + } + + public static void openGif( + Activity contextActivity, Submission submission, int adapterPosition) { + if (SettingValues.gif) { + DataShare.sharedSubmission = submission; + + Intent myIntent = new Intent(contextActivity, MediaView.class); + myIntent.putExtra(MediaView.SUBREDDIT, submission.getSubredditName()); + myIntent.putExtra(EXTRA_SUBMISSION_TITLE, submission.getTitle()); + + GifUtils.AsyncLoadGif.VideoType t = + GifUtils.AsyncLoadGif.getVideoType(submission.getUrl()); + + if (t == GifUtils.AsyncLoadGif.VideoType.VREDDIT) { + if (submission.getDataNode().has("media") + && submission.getDataNode().get("media").has("reddit_video") && submission.getDataNode().get("media").get("reddit_video").has("hls_url")) { + myIntent.putExtra( + MediaView.EXTRA_URL, + StringEscapeUtils.unescapeJson( + submission + .getDataNode() + .get("media") + .get("reddit_video") + .get("dash_url") // In the future, we could load the HLS url as well + .asText()).replace("&", "&")); + } else if (submission.getDataNode().has("media") && submission.getDataNode().get("media").has("reddit_video")) { + myIntent.putExtra( + MediaView.EXTRA_URL, + StringEscapeUtils.unescapeJson(submission.getDataNode().get("media").get("reddit_video").get("fallback_url").asText()).replace("&", "&")); + } else if (submission.getDataNode().has("crosspost_parent_list")) { + myIntent.putExtra( + MediaView.EXTRA_URL, + StringEscapeUtils.unescapeJson( + submission.getDataNode().get("crosspost_parent_list").get(0).get("media").get("reddit_video").get("dash_url").asText()).replace("&", "&")); + } else { + new OpenVRedditTask(contextActivity, submission.getSubredditName()) + .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, submission.getUrl()); + return; + } + + } else if (t.shouldLoadPreview() + && submission.getDataNode().has("preview") + && submission.getDataNode().get("preview").get("images").get(0).has("variants") + && submission.getDataNode().get("preview").get("images").get(0).get("variants").has("mp4")) { + myIntent.putExtra( + MediaView.EXTRA_URL, + StringEscapeUtils.unescapeJson( + submission + .getDataNode() + .get("preview") + .get("images") + .get(0) + .get("variants") + .get("mp4") + .get("source") + .get("url") + .asText()).replace("&", "&")); + } else if (t.shouldLoadPreview() + && submission.getDataNode().has("preview") + && submission.getDataNode().get("preview").has("reddit_video_preview") // Check if reddit_video_preview exists + && submission.getDataNode().get("preview").get("reddit_video_preview").has("fallback_url")) { + myIntent.putExtra( + MediaView.EXTRA_URL, + StringEscapeUtils.unescapeJson( + submission.getDataNode().get("preview").get("reddit_video_preview").get("fallback_url").asText()).replace("&", "&")); + } else if (t == GifUtils.AsyncLoadGif.VideoType.DIRECT + && submission.getDataNode().has("media") + && submission.getDataNode().get("media").has("reddit_video") + && submission.getDataNode().get("media").get("reddit_video").has("fallback_url")) { + myIntent.putExtra( + MediaView.EXTRA_URL, + StringEscapeUtils.unescapeJson( + submission.getDataNode().get("media").get("reddit_video").get("fallback_url").asText()).replace("&", "&")); + } else if (t != GifUtils.AsyncLoadGif.VideoType.OTHER) { + myIntent.putExtra(MediaView.EXTRA_URL, submission.getUrl()); + } else { + LinkUtil.openUrl( + submission.getUrl(), + Palette.getColor(submission.getSubredditName()), + contextActivity, + adapterPosition, + submission); + return; + } + + // Load the preview image which has probably already been cached in memory instead of the direct link + if (submission.getDataNode().has("preview") && submission.getDataNode().get("preview").get("images").get(0).get("source").has("height")) { + String previewUrl = submission.getDataNode().get("preview").get("images").get(0).get("source").get("url").asText(); + myIntent.putExtra(MediaView.EXTRA_DISPLAY_URL, previewUrl); + } + PopulateBase.addAdaptorPosition(myIntent, submission, adapterPosition); + contextActivity.startActivity(myIntent); + } else { + LinkUtil.openExternally(submission.getUrl()); + } + } +} \ No newline at end of file From e2beb13af4db48eb21c28ab10e9ed521b6d24949 Mon Sep 17 00:00:00 2001 From: Nathan Grennan Date: Mon, 31 Mar 2025 17:13:46 -0700 Subject: [PATCH 11/99] Broke addClickFunctions out as SubmissionClickActions.java from PopulateSubmissionViewHolder.java --- .../PopulateSubmissionViewHolder.java | 299 +----------------- .../SubmissionClickActions.java | 244 ++++++++++++++ 2 files changed, 248 insertions(+), 295 deletions(-) create mode 100644 app/src/main/java/me/edgan/redditslide/SubmissionViews/SubmissionClickActions.java diff --git a/app/src/main/java/me/edgan/redditslide/SubmissionViews/PopulateSubmissionViewHolder.java b/app/src/main/java/me/edgan/redditslide/SubmissionViews/PopulateSubmissionViewHolder.java index 87a44101e..1f54d28c5 100644 --- a/app/src/main/java/me/edgan/redditslide/SubmissionViews/PopulateSubmissionViewHolder.java +++ b/app/src/main/java/me/edgan/redditslide/SubmissionViews/PopulateSubmissionViewHolder.java @@ -1,6 +1,5 @@ package me.edgan.redditslide.SubmissionViews; -import static me.edgan.redditslide.Notifications.ImageDownloadNotificationService.EXTRA_SUBMISSION_TITLE; import android.app.Activity; import android.app.Dialog; @@ -14,7 +13,6 @@ import android.graphics.Typeface; import android.graphics.drawable.Drawable; import android.os.AsyncTask; -import android.os.Bundle; import android.text.InputType; import android.text.SpannableStringBuilder; import android.text.style.AbsoluteSizeSpan; @@ -40,33 +38,20 @@ import com.afollestad.materialdialogs.MaterialDialog; import com.cocosw.bottomsheet.BottomSheet; import com.devspark.robototextview.RobotoTypefaces; -import com.fasterxml.jackson.databind.JsonNode; import com.google.android.material.snackbar.Snackbar; import me.edgan.redditslide.ActionStates; -import me.edgan.redditslide.Activities.Album; -import me.edgan.redditslide.Activities.AlbumPager; -import me.edgan.redditslide.Activities.FullscreenVideo; -import me.edgan.redditslide.Activities.GalleryImage; import me.edgan.redditslide.Activities.MainActivity; -import me.edgan.redditslide.Activities.MediaView; import me.edgan.redditslide.Activities.ModQueue; -import me.edgan.redditslide.Activities.MultiredditOverview; import me.edgan.redditslide.Activities.PostReadLater; import me.edgan.redditslide.Activities.Profile; import me.edgan.redditslide.Activities.Reauthenticate; -import me.edgan.redditslide.Activities.RedditGallery; -import me.edgan.redditslide.Activities.RedditGalleryPager; -import me.edgan.redditslide.Activities.Search; import me.edgan.redditslide.Activities.SubredditView; -import me.edgan.redditslide.Activities.Tumblr; -import me.edgan.redditslide.Activities.TumblrPager; import me.edgan.redditslide.Adapters.CommentAdapter; import me.edgan.redditslide.Adapters.SubmissionViewHolder; import me.edgan.redditslide.Authentication; import me.edgan.redditslide.CommentCacheAsync; import me.edgan.redditslide.ContentType; -import me.edgan.redditslide.ForceTouch.PeekViewActivity; import me.edgan.redditslide.HasSeen; import me.edgan.redditslide.Hidden; import me.edgan.redditslide.LastComments; @@ -89,13 +74,11 @@ import me.edgan.redditslide.util.ClipboardUtil; import me.edgan.redditslide.util.CompatUtil; import me.edgan.redditslide.util.DisplayUtil; -import me.edgan.redditslide.util.JsonUtil; import me.edgan.redditslide.util.LayoutUtils; import me.edgan.redditslide.util.LinkUtil; import me.edgan.redditslide.util.NetworkUtil; import me.edgan.redditslide.util.OnSingleClickListener; import me.edgan.redditslide.util.SubmissionParser; -import me.edgan.redditslide.util.SubmissionThumbnailHelper; import net.dean.jraw.ApiException; import net.dean.jraw.fluent.FlairReference; @@ -127,280 +110,6 @@ public class PopulateSubmissionViewHolder { public PopulateSubmissionViewHolder() {} - private static void addClickFunctions( - final View base, - final ContentType.Type type, - final Activity contextActivity, - final Submission submission, - final SubmissionViewHolder holder, - final boolean full) { - base.setOnClickListener( - new OnSingleClickListener() { - @Override - public void onSingleClick(View v) { - if (NetworkUtil.isConnected(contextActivity) - || (!NetworkUtil.isConnected(contextActivity) - && ContentType.fullImage(type))) { - if (SettingValues.storeHistory && !full) { - if (!submission.isNsfw() || SettingValues.storeNSFWHistory) { - HasSeen.addSeen(submission.getFullName()); - if (contextActivity instanceof MainActivity - || contextActivity instanceof MultiredditOverview - || contextActivity instanceof SubredditView - || contextActivity instanceof Search - || contextActivity instanceof Profile) { - holder.title.setAlpha(0.54f); - holder.body.setAlpha(0.54f); - } - } - } - if (!(contextActivity instanceof PeekViewActivity) - || !((PeekViewActivity) contextActivity).isPeeking() - || (base instanceof HeaderImageLinkView - && ((HeaderImageLinkView) base).popped)) { - if (!PostMatch.openExternal(submission.getUrl()) - || type == ContentType.Type.VIDEO) { - switch (type) { - case STREAMABLE: - if (SettingValues.video) { - Intent myIntent = - new Intent( - contextActivity, MediaView.class); - myIntent.putExtra( - MediaView.SUBREDDIT, - submission.getSubredditName()); - myIntent.putExtra( - MediaView.EXTRA_URL, submission.getUrl()); - myIntent.putExtra( - EXTRA_SUBMISSION_TITLE, - submission.getTitle()); - PopulateBase.addAdaptorPosition( - myIntent, - submission, - holder.getBindingAdapterPosition()); - contextActivity.startActivity(myIntent); - } else { - LinkUtil.openExternally(submission.getUrl()); - } - break; - case IMGUR: - case DEVIANTART: - case XKCD: - case IMAGE: - SubmissionThumbnailHelper.openImage( - type, - contextActivity, - submission, - holder.leadImage, - holder.getBindingAdapterPosition()); - break; - case EMBEDDED: - if (SettingValues.video) { - String data = - CompatUtil.fromHtml( - submission - .getDataNode() - .get("media_embed") - .get("content") - .asText()) - .toString(); - { - Intent i = - new Intent( - contextActivity, - FullscreenVideo.class); - i.putExtra(FullscreenVideo.EXTRA_HTML, data); - contextActivity.startActivity(i); - } - } else { - LinkUtil.openExternally(submission.getUrl()); - } - break; - case REDDIT: - SubmissionThumbnailHelper.openRedditContent(submission.getUrl(), contextActivity); - break; - case REDDIT_GALLERY: - if (SettingValues.album) { - Intent i; - if (SettingValues.albumSwipe) { - i = - new Intent( - contextActivity, - RedditGalleryPager.class); - i.putExtra( - AlbumPager.SUBREDDIT, - submission.getSubredditName()); - } else { - i = - new Intent( - contextActivity, - RedditGallery.class); - i.putExtra( - Album.SUBREDDIT, - submission.getSubredditName()); - } - i.putExtra( - EXTRA_SUBMISSION_TITLE, - submission.getTitle()); - - i.putExtra( - RedditGallery.SUBREDDIT, - submission.getSubredditName()); - - ArrayList urls = new ArrayList<>(); - - JsonNode dataNode = submission.getDataNode(); - if (dataNode.has("gallery_data")) { - JsonUtil.getGalleryData(dataNode, urls); - } else if (dataNode.has( - "crosspost_parent_list")) { // Else, try - // getting - // crosspost - // gallery data - JsonNode crosspost_parent = - dataNode.get("crosspost_parent_list") - .get(0); - if (crosspost_parent.has("gallery_data")) { - JsonUtil.getGalleryData( - crosspost_parent, urls); - } - } - - Bundle urlsBundle = new Bundle(); - urlsBundle.putSerializable( - RedditGallery.GALLERY_URLS, urls); - i.putExtras(urlsBundle); - - PopulateBase.addAdaptorPosition( - i, - submission, - holder.getBindingAdapterPosition()); - contextActivity.startActivity(i); - contextActivity.overridePendingTransition( - R.anim.slideright, R.anim.fade_out); - } else { - LinkUtil.openExternally(submission.getUrl()); - } - break; - case LINK: - LinkUtil.openUrl( - submission.getUrl(), - Palette.getColor(submission.getSubredditName()), - contextActivity, - holder.getBindingAdapterPosition(), - submission); - break; - case SELF: - if (holder != null) { - OnSingleClickListener.override = true; - holder.itemView.performClick(); - } - break; - case ALBUM: - if (SettingValues.album) { - Intent i; - if (SettingValues.albumSwipe) { - i = - new Intent( - contextActivity, - AlbumPager.class); - i.putExtra( - AlbumPager.SUBREDDIT, - submission.getSubredditName()); - } else { - i = new Intent(contextActivity, Album.class); - i.putExtra( - Album.SUBREDDIT, - submission.getSubredditName()); - } - i.putExtra( - EXTRA_SUBMISSION_TITLE, - submission.getTitle()); - i.putExtra(Album.EXTRA_URL, submission.getUrl()); - - PopulateBase.addAdaptorPosition( - i, - submission, - holder.getBindingAdapterPosition()); - contextActivity.startActivity(i); - contextActivity.overridePendingTransition( - R.anim.slideright, R.anim.fade_out); - } else { - LinkUtil.openExternally(submission.getUrl()); - } - break; - case TUMBLR: - if (SettingValues.album) { - Intent i; - if (SettingValues.albumSwipe) { - i = - new Intent( - contextActivity, - TumblrPager.class); - i.putExtra( - TumblrPager.SUBREDDIT, - submission.getSubredditName()); - } else { - i = new Intent(contextActivity, Tumblr.class); - i.putExtra( - Tumblr.SUBREDDIT, - submission.getSubredditName()); - } - i.putExtra(Album.EXTRA_URL, submission.getUrl()); - - PopulateBase.addAdaptorPosition( - i, - submission, - holder.getBindingAdapterPosition()); - contextActivity.startActivity(i); - contextActivity.overridePendingTransition( - R.anim.slideright, R.anim.fade_out); - } else { - LinkUtil.openExternally(submission.getUrl()); - } - break; - case VREDDIT_REDIRECT: - case GIF: - case VREDDIT_DIRECT: - SubmissionThumbnailHelper.openGif( - contextActivity, - submission, - holder.getBindingAdapterPosition()); - break; - case NONE: - if (holder != null) { - holder.itemView.performClick(); - } - break; - case VIDEO: - if (!LinkUtil.tryOpenWithVideoPlugin( - submission.getUrl())) { - LinkUtil.openUrl( - submission.getUrl(), - Palette.getStatusBarColor(), - contextActivity); - } - break; - } - } else { - LinkUtil.openExternally(submission.getUrl()); - } - } - } else { - if (!(contextActivity instanceof PeekViewActivity) - || !((PeekViewActivity) contextActivity).isPeeking()) { - - Snackbar s = - Snackbar.make( - holder.itemView, - R.string.go_online_view_content, - Snackbar.LENGTH_SHORT); - LayoutUtils.showSnackbar(s); - } - } - } - }); - } public String reason; @@ -3011,14 +2720,14 @@ public void onClick(View v) { final ContentType.Type type = ContentType.getContentType(submission); - addClickFunctions(holder.leadImage, type, mContext, submission, holder, full); + SubmissionClickActions.addClickFunctions(holder.leadImage, type, mContext, submission, holder, full); if (thumbImage2 != null) { - addClickFunctions(thumbImage2, type, mContext, submission, holder, full); + SubmissionClickActions.addClickFunctions(thumbImage2, type, mContext, submission, holder, full); } if (full) { - addClickFunctions( + SubmissionClickActions.addClickFunctions( holder.itemView.findViewById(R.id.wraparea), type, mContext, @@ -4141,4 +3850,4 @@ protected void onPostExecute(Void aVoid) { } } } -} +} \ No newline at end of file diff --git a/app/src/main/java/me/edgan/redditslide/SubmissionViews/SubmissionClickActions.java b/app/src/main/java/me/edgan/redditslide/SubmissionViews/SubmissionClickActions.java new file mode 100644 index 000000000..de14e54c3 --- /dev/null +++ b/app/src/main/java/me/edgan/redditslide/SubmissionViews/SubmissionClickActions.java @@ -0,0 +1,244 @@ +package me.edgan.redditslide.SubmissionViews; + +import static me.edgan.redditslide.Notifications.ImageDownloadNotificationService.EXTRA_SUBMISSION_TITLE; + +import android.app.Activity; +import android.content.Intent; +import android.os.Bundle; +import android.view.View; + + +import com.fasterxml.jackson.databind.JsonNode; +import com.google.android.material.snackbar.Snackbar; + +import net.dean.jraw.models.Submission; + +import java.util.ArrayList; + +import me.edgan.redditslide.Activities.Album; +import me.edgan.redditslide.Activities.AlbumPager; +import me.edgan.redditslide.Activities.FullscreenVideo; +import me.edgan.redditslide.Activities.GalleryImage; +import me.edgan.redditslide.Activities.MainActivity; +import me.edgan.redditslide.Activities.MediaView; +import me.edgan.redditslide.Activities.MultiredditOverview; +import me.edgan.redditslide.Activities.Profile; +import me.edgan.redditslide.Activities.RedditGallery; +import me.edgan.redditslide.Activities.RedditGalleryPager; +import me.edgan.redditslide.Activities.Search; +import me.edgan.redditslide.Activities.SubredditView; +import me.edgan.redditslide.Activities.Tumblr; +import me.edgan.redditslide.Activities.TumblrPager; +import me.edgan.redditslide.Adapters.SubmissionViewHolder; +import me.edgan.redditslide.ContentType; +import me.edgan.redditslide.ForceTouch.PeekViewActivity; +import me.edgan.redditslide.HasSeen; +import me.edgan.redditslide.R; +import me.edgan.redditslide.SettingValues; +import me.edgan.redditslide.Visuals.Palette; +import me.edgan.redditslide.util.CompatUtil; +import me.edgan.redditslide.util.JsonUtil; +import me.edgan.redditslide.util.LayoutUtils; +import me.edgan.redditslide.util.LinkUtil; +import me.edgan.redditslide.util.NetworkUtil; +import me.edgan.redditslide.util.OnSingleClickListener; +import me.edgan.redditslide.PostMatch; +import me.edgan.redditslide.util.SubmissionThumbnailHelper; + +/** + * Handles click actions for Submission views. + */ +public class SubmissionClickActions { + + public static void addClickFunctions( + final View base, + final ContentType.Type type, + final Activity contextActivity, + final Submission submission, + final SubmissionViewHolder holder, + final boolean full) { + base.setOnClickListener( + new OnSingleClickListener() { + @Override + public void onSingleClick(View v) { + if (NetworkUtil.isConnected(contextActivity) + || (!NetworkUtil.isConnected(contextActivity) + && ContentType.fullImage(type))) { + if (SettingValues.storeHistory && !full) { + if (!submission.isNsfw() || SettingValues.storeNSFWHistory) { + HasSeen.addSeen(submission.getFullName()); + if (contextActivity instanceof MainActivity + || contextActivity instanceof MultiredditOverview + || contextActivity instanceof SubredditView + || contextActivity instanceof Search + || contextActivity instanceof Profile) { + holder.title.setAlpha(0.54f); + holder.body.setAlpha(0.54f); + } + } + } + if (!(contextActivity instanceof PeekViewActivity) + || !((PeekViewActivity) contextActivity).isPeeking() + || (base instanceof HeaderImageLinkView && ((HeaderImageLinkView) base).popped)) { + if (!PostMatch.openExternal(submission.getUrl()) + || type == ContentType.Type.VIDEO) { + switch (type) { + case STREAMABLE: + if (SettingValues.video) { + Intent myIntent = new Intent(contextActivity, MediaView.class); + myIntent.putExtra(MediaView.SUBREDDIT, submission.getSubredditName()); + myIntent.putExtra(MediaView.EXTRA_URL, submission.getUrl()); + myIntent.putExtra(EXTRA_SUBMISSION_TITLE, submission.getTitle()); + PopulateBase.addAdaptorPosition(myIntent, submission, holder.getBindingAdapterPosition()); + contextActivity.startActivity(myIntent); + } else { + LinkUtil.openExternally(submission.getUrl()); + } + + break; + case IMGUR: + case DEVIANTART: + case XKCD: + case IMAGE: + SubmissionThumbnailHelper.openImage(type, contextActivity, submission, holder.leadImage, holder.getBindingAdapterPosition()); + break; + case EMBEDDED: + if (SettingValues.video) { + String data = CompatUtil.fromHtml(submission.getDataNode().get("media_embed").get("content").asText()).toString(); + { + Intent i = new Intent(contextActivity, FullscreenVideo.class); + i.putExtra(FullscreenVideo.EXTRA_HTML, data); + contextActivity.startActivity(i); + } + } else { + LinkUtil.openExternally(submission.getUrl()); + } + break; + case REDDIT: + SubmissionThumbnailHelper.openRedditContent(submission.getUrl(), contextActivity); + break; + case REDDIT_GALLERY: + if (SettingValues.album) { + Intent i; + if (SettingValues.albumSwipe) { + i = new Intent(contextActivity, RedditGalleryPager.class); + i.putExtra(AlbumPager.SUBREDDIT, submission.getSubredditName()); + } else { + i = new Intent(contextActivity, RedditGallery.class); + i.putExtra(Album.SUBREDDIT, submission.getSubredditName()); + } + + i.putExtra(EXTRA_SUBMISSION_TITLE, submission.getTitle()); + i.putExtra(RedditGallery.SUBREDDIT, submission.getSubredditName()); + + ArrayList urls = new ArrayList<>(); + + JsonNode dataNode = submission.getDataNode(); + + if (dataNode.has("gallery_data")) { + JsonUtil.getGalleryData(dataNode, urls); + } else if (dataNode.has("crosspost_parent_list")) { // Else, try getting crosspost gallery data + JsonNode crosspost_parent = dataNode.get("crosspost_parent_list").get(0); + if (crosspost_parent.has("gallery_data")) { + JsonUtil.getGalleryData(crosspost_parent, urls); + } + } + + Bundle urlsBundle = new Bundle(); + urlsBundle.putSerializable(RedditGallery.GALLERY_URLS, urls); + i.putExtras(urlsBundle); + + PopulateBase.addAdaptorPosition(i, submission, holder.getBindingAdapterPosition()); + contextActivity.startActivity(i); + contextActivity.overridePendingTransition(R.anim.slideright, R.anim.fade_out); + } else { + LinkUtil.openExternally(submission.getUrl()); + } + break; + case LINK: + LinkUtil.openUrl( + submission.getUrl(), + Palette.getColor(submission.getSubredditName()), + contextActivity, + holder.getBindingAdapterPosition(), + submission); + break; + case SELF: + if (holder != null) { + OnSingleClickListener.override = true; + holder.itemView.performClick(); + } + break; + case ALBUM: + if (SettingValues.album) { + Intent i; + if (SettingValues.albumSwipe) { + i = new Intent(contextActivity, AlbumPager.class); + i.putExtra(AlbumPager.SUBREDDIT, submission.getSubredditName()); + } else { + i = new Intent(contextActivity, Album.class); + i.putExtra(Album.SUBREDDIT, submission.getSubredditName()); + } + + i.putExtra(EXTRA_SUBMISSION_TITLE, submission.getTitle()); + i.putExtra(Album.EXTRA_URL, submission.getUrl()); + + PopulateBase.addAdaptorPosition(i, submission, holder.getBindingAdapterPosition()); + contextActivity.startActivity(i); + contextActivity.overridePendingTransition(R.anim.slideright, R.anim.fade_out); + } else { + LinkUtil.openExternally(submission.getUrl()); + } + break; + case TUMBLR: + if (SettingValues.album) { + Intent i; + if (SettingValues.albumSwipe) { + i = new Intent(contextActivity, TumblrPager.class); + i.putExtra(TumblrPager.SUBREDDIT, submission.getSubredditName()); + } else { + i = new Intent(contextActivity, Tumblr.class); + i.putExtra(Tumblr.SUBREDDIT, submission.getSubredditName()); + } + i.putExtra(Album.EXTRA_URL, submission.getUrl()); + + PopulateBase.addAdaptorPosition(i, submission, holder.getBindingAdapterPosition()); + contextActivity.startActivity(i); + contextActivity.overridePendingTransition(R.anim.slideright, R.anim.fade_out); + } else { + LinkUtil.openExternally(submission.getUrl()); + } + break; + case VREDDIT_REDIRECT: + case GIF: + case VREDDIT_DIRECT: + SubmissionThumbnailHelper.openGif(contextActivity, submission, holder.getBindingAdapterPosition()); + break; + case NONE: + if (holder != null) { + holder.itemView.performClick(); + } + + break; + case VIDEO: + if (!LinkUtil.tryOpenWithVideoPlugin(submission.getUrl())) { + LinkUtil.openUrl(submission.getUrl(), Palette.getStatusBarColor(), contextActivity); + } + + break; + } + } else { + LinkUtil.openExternally(submission.getUrl()); + } + } + } else { + if (!(contextActivity instanceof PeekViewActivity) || !((PeekViewActivity) contextActivity).isPeeking()) { + + Snackbar s = Snackbar.make(holder.itemView, R.string.go_online_view_content, Snackbar.LENGTH_SHORT); + LayoutUtils.showSnackbar(s); + } + } + } + }); + } +} \ No newline at end of file From 055e2a1e273e6b2be92e4e2b00e26553ca612119 Mon Sep 17 00:00:00 2001 From: Nathan Grennan Date: Mon, 31 Mar 2025 20:04:57 -0700 Subject: [PATCH 12/99] Broke out SubmissionBottomSheetActions.java from PopulateSubmissionViewHolder.java --- .../PopulateSubmissionViewHolder.java | 1126 +---------------- .../util/SubmissionBottomSheetActions.java | 779 ++++++++++++ 2 files changed, 786 insertions(+), 1119 deletions(-) create mode 100644 app/src/main/java/me/edgan/redditslide/util/SubmissionBottomSheetActions.java diff --git a/app/src/main/java/me/edgan/redditslide/SubmissionViews/PopulateSubmissionViewHolder.java b/app/src/main/java/me/edgan/redditslide/SubmissionViews/PopulateSubmissionViewHolder.java index 1f54d28c5..27ac5c5e2 100644 --- a/app/src/main/java/me/edgan/redditslide/SubmissionViews/PopulateSubmissionViewHolder.java +++ b/app/src/main/java/me/edgan/redditslide/SubmissionViews/PopulateSubmissionViewHolder.java @@ -6,7 +6,6 @@ import android.content.Context; import android.content.DialogInterface; import android.content.Intent; -import android.content.SharedPreferences; import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Color; @@ -22,10 +21,7 @@ import android.widget.EditText; import android.widget.ImageView; import android.widget.LinearLayout; -import android.widget.RadioButton; -import android.widget.RadioGroup; import android.widget.TextView; -import android.widget.Toast; import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; @@ -43,21 +39,15 @@ import me.edgan.redditslide.ActionStates; import me.edgan.redditslide.Activities.MainActivity; import me.edgan.redditslide.Activities.ModQueue; -import me.edgan.redditslide.Activities.PostReadLater; import me.edgan.redditslide.Activities.Profile; import me.edgan.redditslide.Activities.Reauthenticate; -import me.edgan.redditslide.Activities.SubredditView; import me.edgan.redditslide.Adapters.CommentAdapter; import me.edgan.redditslide.Adapters.SubmissionViewHolder; import me.edgan.redditslide.Authentication; -import me.edgan.redditslide.CommentCacheAsync; import me.edgan.redditslide.ContentType; import me.edgan.redditslide.HasSeen; -import me.edgan.redditslide.Hidden; import me.edgan.redditslide.LastComments; -import me.edgan.redditslide.OfflineSubreddit; import me.edgan.redditslide.OpenRedditLink; -import me.edgan.redditslide.PostMatch; import me.edgan.redditslide.R; import me.edgan.redditslide.Reddit; import me.edgan.redditslide.SettingValues; @@ -71,13 +61,11 @@ import me.edgan.redditslide.Vote; import me.edgan.redditslide.util.AnimatorUtil; import me.edgan.redditslide.util.BlendModeUtil; -import me.edgan.redditslide.util.ClipboardUtil; import me.edgan.redditslide.util.CompatUtil; import me.edgan.redditslide.util.DisplayUtil; import me.edgan.redditslide.util.LayoutUtils; -import me.edgan.redditslide.util.LinkUtil; -import me.edgan.redditslide.util.NetworkUtil; import me.edgan.redditslide.util.OnSingleClickListener; +import me.edgan.redditslide.util.SubmissionBottomSheetActions; import me.edgan.redditslide.util.SubmissionParser; import net.dean.jraw.ApiException; @@ -90,9 +78,7 @@ import net.dean.jraw.models.Contribution; import net.dean.jraw.models.DistinguishedStatus; import net.dean.jraw.models.FlairTemplate; -import net.dean.jraw.models.Ruleset; import net.dean.jraw.models.Submission; -import net.dean.jraw.models.SubredditRule; import net.dean.jraw.models.Thing; import net.dean.jraw.models.VoteDirection; @@ -100,7 +86,6 @@ import java.util.ArrayList; import java.util.Arrays; -import java.util.Collections; import java.util.List; import java.util.Locale; import java.util.Map; @@ -111,1071 +96,7 @@ public class PopulateSubmissionViewHolder { public PopulateSubmissionViewHolder() {} - public String reason; - boolean[] chosen = new boolean[] {false, false, false}; - boolean[] oldChosen = new boolean[] {false, false, false}; - - public void showBottomSheet( - final Activity mContext, - final Submission submission, - final SubmissionViewHolder holder, - final List posts, - final String baseSub, - final RecyclerView recyclerview, - final boolean full) { - - int[] attrs = new int[] {R.attr.tintColor}; - TypedArray ta = mContext.obtainStyledAttributes(attrs); - - int color = ta.getColor(0, Color.WHITE); - Drawable profile = - ResourcesCompat.getDrawable( - mContext.getResources(), R.drawable.ic_account_circle, null); - final Drawable sub = - ResourcesCompat.getDrawable( - mContext.getResources(), R.drawable.ic_bookmark_border, null); - Drawable saved = - ResourcesCompat.getDrawable(mContext.getResources(), R.drawable.ic_star, null); - Drawable hide = - ResourcesCompat.getDrawable( - mContext.getResources(), R.drawable.ic_visibility_off, null); - final Drawable report = - ResourcesCompat.getDrawable(mContext.getResources(), R.drawable.ic_report, null); - Drawable copy = - ResourcesCompat.getDrawable( - mContext.getResources(), R.drawable.ic_content_copy, null); - final Drawable readLater = - ResourcesCompat.getDrawable(mContext.getResources(), R.drawable.ic_download, null); - Drawable open = - ResourcesCompat.getDrawable( - mContext.getResources(), R.drawable.ic_open_in_browser, null); - Drawable link = - ResourcesCompat.getDrawable(mContext.getResources(), R.drawable.ic_link, null); - Drawable reddit = - ResourcesCompat.getDrawable(mContext.getResources(), R.drawable.ic_forum, null); - Drawable filter = - ResourcesCompat.getDrawable( - mContext.getResources(), R.drawable.ic_filter_list, null); - Drawable crosspost = - ResourcesCompat.getDrawable(mContext.getResources(), R.drawable.ic_forward, null); - - final List drawableSet = - Arrays.asList( - profile, sub, saved, hide, report, copy, open, link, reddit, readLater, - filter, crosspost); - BlendModeUtil.tintDrawablesAsSrcAtop(drawableSet, color); - - ta.recycle(); - - final BottomSheet.Builder b = - new BottomSheet.Builder(mContext).title(CompatUtil.fromHtml(submission.getTitle())); - - final boolean isReadLater = mContext instanceof PostReadLater; - final boolean isAddedToReadLaterList = ReadLater.isToBeReadLater(submission); - if (Authentication.didOnline) { - b.sheet(1, profile, "/u/" + submission.getAuthor()) - .sheet(2, sub, "/r/" + submission.getSubredditName()); - String save = mContext.getString(R.string.btn_save); - if (ActionStates.isSaved(submission)) { - save = mContext.getString(R.string.comment_unsave); - } - if (Authentication.isLoggedIn) { - b.sheet(3, saved, save); - } - } - - if (isAddedToReadLaterList) { - CharSequence markAsReadCs = mContext.getString(R.string.mark_as_read); - b.sheet(28, readLater, markAsReadCs); - } else { - CharSequence readLaterCs = mContext.getString(R.string.read_later); - b.sheet(28, readLater, readLaterCs); - } - - if (Authentication.didOnline) { - if (Authentication.isLoggedIn) { - b.sheet(12, report, mContext.getString(R.string.btn_report)); - b.sheet(13, crosspost, mContext.getString(R.string.btn_crosspost)); - } - } - - if (submission.getSelftext() != null && !submission.getSelftext().isEmpty() && full) { - b.sheet(25, copy, mContext.getString(R.string.submission_copy_text)); - } - - boolean hidden = submission.isHidden(); - if (!full && Authentication.didOnline) { - if (!hidden) { - b.sheet(5, hide, mContext.getString(R.string.submission_hide)); - } else { - b.sheet(5, hide, mContext.getString(R.string.submission_unhide)); - } - } - b.sheet(7, open, mContext.getString(R.string.open_externally)); - - b.sheet(4, link, mContext.getString(R.string.submission_share_permalink)) - .sheet(8, reddit, mContext.getString(R.string.submission_share_reddit_url)); - if ((mContext instanceof MainActivity) || (mContext instanceof SubredditView)) { - b.sheet(10, filter, mContext.getString(R.string.filter_content)); - } - - b.listener( - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - switch (which) { - case 1: - { - Intent i = new Intent(mContext, Profile.class); - i.putExtra(Profile.EXTRA_PROFILE, submission.getAuthor()); - mContext.startActivity(i); - } - break; - case 2: - { - Intent i = new Intent(mContext, SubredditView.class); - i.putExtra( - SubredditView.EXTRA_SUBREDDIT, - submission.getSubredditName()); - mContext.startActivityForResult(i, 14); - } - break; - case 10: - String[] choices; - final String flair = - submission.getSubmissionFlair().getText() != null - ? submission.getSubmissionFlair().getText() - : ""; - if (flair.isEmpty()) { - choices = - new String[] { - mContext.getString( - R.string.filter_posts_sub, - submission.getSubredditName()), - mContext.getString( - R.string.filter_posts_user, - submission.getAuthor()), - mContext.getString( - R.string.filter_posts_urls, - submission.getDomain()), - mContext.getString( - R.string.filter_open_externally, - submission.getDomain()) - }; - - chosen = - new boolean[] { - SettingValues.subredditFilters.contains( - submission - .getSubredditName() - .toLowerCase(Locale.ENGLISH)), - SettingValues.userFilters.contains( - submission - .getAuthor() - .toLowerCase(Locale.ENGLISH)), - SettingValues.domainFilters.contains( - submission - .getDomain() - .toLowerCase(Locale.ENGLISH)), - SettingValues.alwaysExternal.contains( - submission - .getDomain() - .toLowerCase(Locale.ENGLISH)) - }; - oldChosen = chosen.clone(); - } else { - choices = - new String[] { - mContext.getString( - R.string.filter_posts_sub, - submission.getSubredditName()), - mContext.getString( - R.string.filter_posts_user, - submission.getAuthor()), - mContext.getString( - R.string.filter_posts_urls, - submission.getDomain()), - mContext.getString( - R.string.filter_open_externally, - submission.getDomain()), - mContext.getString( - R.string.filter_posts_flair, flair, baseSub) - }; - } - chosen = - new boolean[] { - SettingValues.subredditFilters.contains( - submission - .getSubredditName() - .toLowerCase(Locale.ENGLISH)), - SettingValues.userFilters.contains( - submission - .getAuthor() - .toLowerCase(Locale.ENGLISH)), - SettingValues.domainFilters.contains( - submission - .getDomain() - .toLowerCase(Locale.ENGLISH)), - SettingValues.alwaysExternal.contains( - submission - .getDomain() - .toLowerCase(Locale.ENGLISH)), - SettingValues.flairFilters.contains( - baseSub - + ":" - + flair.toLowerCase(Locale.ENGLISH) - .trim()) - }; - oldChosen = chosen.clone(); - - new AlertDialog.Builder(mContext) - .setTitle(R.string.filter_title) - .setMultiChoiceItems( - choices, - chosen, - (dialog1, which1, isChecked) -> - chosen[which1] = isChecked) - .setPositiveButton( - R.string.filter_btn, - (dialog12, which12) -> { - boolean filtered = false; - SharedPreferences.Editor e = - SettingValues.prefs.edit(); - if (chosen[0] && chosen[0] != oldChosen[0]) { - SettingValues.subredditFilters.add( - submission - .getSubredditName() - .toLowerCase(Locale.ENGLISH) - .trim()); - filtered = true; - e.putStringSet( - SettingValues - .PREF_SUBREDDIT_FILTERS, - SettingValues.subredditFilters); - } else if (!chosen[0] - && chosen[0] != oldChosen[0]) { - SettingValues.subredditFilters.remove( - submission - .getSubredditName() - .toLowerCase(Locale.ENGLISH) - .trim()); - filtered = false; - e.putStringSet( - SettingValues - .PREF_SUBREDDIT_FILTERS, - SettingValues.subredditFilters); - e.apply(); - } - if (chosen[1] && chosen[1] != oldChosen[1]) { - SettingValues.userFilters.add( - submission - .getAuthor() - .toLowerCase(Locale.ENGLISH) - .trim()); - filtered = true; - e.putStringSet( - SettingValues.PREF_USER_FILTERS, - SettingValues.userFilters); - } else if (!chosen[1] - && chosen[1] != oldChosen[1]) { - SettingValues.userFilters.remove( - submission - .getAuthor() - .toLowerCase(Locale.ENGLISH) - .trim()); - filtered = false; - e.putStringSet( - SettingValues.PREF_USER_FILTERS, - SettingValues.userFilters); - e.apply(); - } - if (chosen[2] && chosen[2] != oldChosen[2]) { - SettingValues.domainFilters.add( - submission - .getDomain() - .toLowerCase(Locale.ENGLISH) - .trim()); - filtered = true; - e.putStringSet( - SettingValues.PREF_DOMAIN_FILTERS, - SettingValues.domainFilters); - } else if (!chosen[2] - && chosen[2] != oldChosen[2]) { - SettingValues.domainFilters.remove( - submission - .getDomain() - .toLowerCase(Locale.ENGLISH) - .trim()); - filtered = false; - e.putStringSet( - SettingValues.PREF_DOMAIN_FILTERS, - SettingValues.domainFilters); - e.apply(); - } - if (chosen[3] && chosen[3] != oldChosen[3]) { - SettingValues.alwaysExternal.add( - submission - .getDomain() - .toLowerCase(Locale.ENGLISH) - .trim()); - e.putStringSet( - SettingValues.PREF_ALWAYS_EXTERNAL, - SettingValues.alwaysExternal); - e.apply(); - } else if (!chosen[3] - && chosen[3] != oldChosen[3]) { - SettingValues.alwaysExternal.remove( - submission - .getDomain() - .toLowerCase(Locale.ENGLISH) - .trim()); - e.putStringSet( - SettingValues.PREF_ALWAYS_EXTERNAL, - SettingValues.alwaysExternal); - e.apply(); - } - if (chosen.length > 4) { - String s = - (baseSub + ":" + flair) - .toLowerCase(Locale.ENGLISH) - .trim(); - if (chosen[4] - && chosen[4] != oldChosen[4]) { - SettingValues.flairFilters.add(s); - e.putStringSet( - SettingValues - .PREF_FLAIR_FILTERS, - SettingValues.flairFilters); - e.apply(); - filtered = true; - } else if (!chosen[4] - && chosen[4] != oldChosen[4]) { - SettingValues.flairFilters.remove(s); - e.putStringSet( - SettingValues - .PREF_FLAIR_FILTERS, - SettingValues.flairFilters); - e.apply(); - } - } - if (filtered) { - e.apply(); - ArrayList toRemove = - new ArrayList<>(); - for (Contribution s : posts) { - if (s instanceof Submission - && PostMatch.doesMatch( - (Submission) s)) { - toRemove.add(s); - } - } - - OfflineSubreddit s = - OfflineSubreddit.getSubreddit( - baseSub, false, mContext); - - for (Contribution remove : toRemove) { - final int pos = posts.indexOf(remove); - posts.remove(pos); - if (baseSub != null) { - s.hideMulti(pos); - } - } - s.writeToMemoryNoStorage(); - recyclerview - .getAdapter() - .notifyDataSetChanged(); - } - }) - .setNegativeButton(R.string.btn_cancel, null) - .show(); - break; - - case 3: - saveSubmission(submission, mContext, holder, full); - break; - case 5: - { - hideSubmission( - submission, posts, baseSub, recyclerview, mContext); - } - break; - case 7: - LinkUtil.openExternally(submission.getUrl()); - if (submission.isNsfw() && !SettingValues.storeNSFWHistory) { - // Do nothing if the post is NSFW and storeNSFWHistory is not - // enabled - } else if (SettingValues.storeHistory) { - HasSeen.addSeen(submission.getFullName()); - } - break; - case 13: - LinkUtil.crosspost(submission, mContext); - break; - case 28: - if (!isAddedToReadLaterList) { - ReadLater.setReadLater(submission, true); - Snackbar s = - Snackbar.make( - holder.itemView, - "Added to read later!", - Snackbar.LENGTH_SHORT); - View view = s.getView(); - TextView tv = - view.findViewById( - com.google.android.material.R.id.snackbar_text); - tv.setTextColor(Color.WHITE); - s.setAction( - R.string.btn_undo, - new View.OnClickListener() { - @Override - public void onClick(View view) { - ReadLater.setReadLater(submission, false); - Snackbar s2 = - Snackbar.make( - holder.itemView, - "Removed from read later", - Snackbar.LENGTH_SHORT); - LayoutUtils.showSnackbar(s2); - } - }); - if (NetworkUtil.isConnected(mContext)) { - new CommentCacheAsync( - Collections.singletonList(submission), - mContext, - CommentCacheAsync.SAVED_SUBMISSIONS, - new boolean[] {true, true}) - .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } - s.show(); - } else { - ReadLater.setReadLater(submission, false); - if (isReadLater || !Authentication.didOnline) { - final int pos = posts.indexOf(submission); - posts.remove(submission); - - recyclerview - .getAdapter() - .notifyItemRemoved( - holder.getBindingAdapterPosition()); - - Snackbar s2 = - Snackbar.make( - holder.itemView, - "Removed from read later", - Snackbar.LENGTH_SHORT); - View view2 = s2.getView(); - TextView tv2 = - view2.findViewById( - com.google.android.material.R.id - .snackbar_text); - tv2.setTextColor(Color.WHITE); - s2.setAction( - R.string.btn_undo, - new View.OnClickListener() { - @Override - public void onClick(View view) { - posts.add(pos, (T) submission); - recyclerview - .getAdapter() - .notifyDataSetChanged(); - } - }); - } else { - Snackbar s2 = - Snackbar.make( - holder.itemView, - "Removed from read later", - Snackbar.LENGTH_SHORT); - View view2 = s2.getView(); - TextView tv2 = - view2.findViewById( - com.google.android.material.R.id - .snackbar_text); - s2.show(); - } - OfflineSubreddit.newSubreddit( - CommentCacheAsync.SAVED_SUBMISSIONS) - .deleteFromMemory(submission.getFullName()); - } - break; - case 4: - Reddit.defaultShareText( - CompatUtil.fromHtml(submission.getTitle()).toString(), - StringEscapeUtils.escapeHtml4(submission.getUrl()), - mContext); - break; - case 12: - final MaterialDialog reportDialog = - new MaterialDialog.Builder(mContext) - .customView(R.layout.report_dialog, true) - .title(R.string.report_post) - .positiveText(R.string.btn_report) - .negativeText(R.string.btn_cancel) - .onPositive( - new MaterialDialog.SingleButtonCallback() { - @Override - public void onClick( - MaterialDialog dialog, - DialogAction which) { - RadioGroup reasonGroup = - dialog.getCustomView() - .findViewById( - R.id - .report_reasons); - String reportReason; - if (reasonGroup - .getCheckedRadioButtonId() - == R.id.report_other) { - reportReason = - ((EditText) - dialog.getCustomView() - .findViewById( - R - .id - .input_report_reason)) - .getText() - .toString(); - } else { - reportReason = - ((RadioButton) - reasonGroup - .findViewById( - reasonGroup - .getCheckedRadioButtonId())) - .getText() - .toString(); - } - new AsyncReportTask( - submission, - holder.itemView) - .executeOnExecutor( - AsyncTask - .THREAD_POOL_EXECUTOR, - reportReason); - } - }) - .build(); - - final RadioGroup reasonGroup = - reportDialog - .getCustomView() - .findViewById(R.id.report_reasons); - - reasonGroup.setOnCheckedChangeListener( - new RadioGroup.OnCheckedChangeListener() { - @Override - public void onCheckedChanged( - RadioGroup group, int checkedId) { - if (checkedId == R.id.report_other) - reportDialog - .getCustomView() - .findViewById(R.id.input_report_reason) - .setVisibility(View.VISIBLE); - else - reportDialog - .getCustomView() - .findViewById(R.id.input_report_reason) - .setVisibility(View.GONE); - } - }); - - // Load sub's report reasons and show the appropriate ones - new AsyncTask() { - @Override - protected Ruleset doInBackground(Void... voids) { - return Authentication.reddit.getRules( - submission.getSubredditName()); - } - - @Override - protected void onPostExecute(Ruleset rules) { - reportDialog - .getCustomView() - .findViewById(R.id.report_loading) - .setVisibility(View.GONE); - if (rules.getSubredditRules().size() > 0) { - TextView subHeader = new TextView(mContext); - subHeader.setText( - mContext.getString( - R.string.report_sub_rules, - submission.getSubredditName())); - reasonGroup.addView( - subHeader, reasonGroup.getChildCount() - 2); - } - for (SubredditRule rule : rules.getSubredditRules()) { - if (rule.getKind() == SubredditRule.RuleKind.LINK - || rule.getKind() - == SubredditRule.RuleKind.ALL) { - RadioButton btn = new RadioButton(mContext); - btn.setText(rule.getViolationReason()); - reasonGroup.addView( - btn, reasonGroup.getChildCount() - 2); - btn.getLayoutParams().width = - WindowManager.LayoutParams.MATCH_PARENT; - } - } - if (rules.getSiteRules().size() > 0) { - TextView siteHeader = new TextView(mContext); - siteHeader.setText(R.string.report_site_rules); - reasonGroup.addView( - siteHeader, reasonGroup.getChildCount() - 2); - } - for (String rule : rules.getSiteRules()) { - RadioButton btn = new RadioButton(mContext); - btn.setText(rule); - reasonGroup.addView( - btn, reasonGroup.getChildCount() - 2); - btn.getLayoutParams().width = - WindowManager.LayoutParams.MATCH_PARENT; - } - } - }.execute(); - - reportDialog.show(); - break; - case 8: - if (SettingValues.shareLongLink) { - Reddit.defaultShareText( - submission.getTitle(), - "https://reddit.com" + submission.getPermalink(), - mContext); - } else { - Reddit.defaultShareText( - submission.getTitle(), - "https://reddit.com/comments/" + submission.getId(), - mContext); - } - break; - case 6: - { - ClipboardUtil.copyToClipboard( - mContext, "Link", submission.getUrl()); - Toast.makeText( - mContext, - R.string.submission_link_copied, - Toast.LENGTH_SHORT) - .show(); - } - break; - case 25: - final TextView showText = new TextView(mContext); - showText.setText( - StringEscapeUtils.unescapeHtml4( - submission.getTitle() - + "\n\n" - + submission.getSelftext())); - showText.setTextIsSelectable(true); - int sixteen = DisplayUtil.dpToPxVertical(24); - showText.setPadding(sixteen, 0, sixteen, 0); - new AlertDialog.Builder(mContext) - .setView(showText) - .setTitle("Select text to copy") - .setCancelable(true) - .setPositiveButton( - "COPY SELECTED", - (dialog13, which13) -> { - String selected = - showText.getText() - .toString() - .substring( - showText - .getSelectionStart(), - showText - .getSelectionEnd()); - if (!selected.isEmpty()) { - ClipboardUtil.copyToClipboard( - mContext, "Selftext", selected); - } else { - ClipboardUtil.copyToClipboard( - mContext, - "Selftext", - CompatUtil.fromHtml( - submission.getTitle() - + "\n\n" - + submission - .getSelftext())); - } - Toast.makeText( - mContext, - R.string - .submission_comment_copied, - Toast.LENGTH_SHORT) - .show(); - }) - .setNegativeButton(R.string.btn_cancel, null) - .setNeutralButton( - "COPY ALL", - (dialog14, which14) -> { - ClipboardUtil.copyToClipboard( - mContext, - "Selftext", - StringEscapeUtils.unescapeHtml4( - submission.getTitle() - + "\n\n" - + submission - .getSelftext())); - - Toast.makeText( - mContext, - R.string.submission_text_copied, - Toast.LENGTH_SHORT) - .show(); - }) - .show(); - break; - } - } - }); - b.show(); - } - - private void saveSubmission( - final Submission submission, - final Activity mContext, - final SubmissionViewHolder holder, - final boolean full) { - new AsyncTask() { - @Override - protected Void doInBackground(Void... params) { - try { - if (ActionStates.isSaved(submission)) { - new AccountManager(Authentication.reddit).unsave(submission); - ActionStates.setSaved(submission, false); - } else { - new AccountManager(Authentication.reddit).save(submission); - ActionStates.setSaved(submission, true); - } - - } catch (Exception e) { - e.printStackTrace(); - } - - return null; - } - - @Override - protected void onPostExecute(Void aVoid) { - Snackbar s; - try { - if (ActionStates.isSaved(submission)) { - - BlendModeUtil.tintImageViewAsSrcAtop( - (ImageView) holder.save, - ContextCompat.getColor(mContext, R.color.md_amber_500)); - holder.save.setContentDescription(mContext.getString(R.string.btn_unsave)); - s = - Snackbar.make( - holder.itemView, - R.string.submission_info_saved, - Snackbar.LENGTH_LONG); - if (Authentication.me.hasGold()) { - s.setAction( - R.string.category_categorize, - new View.OnClickListener() { - @Override - public void onClick(View v) { - categorizeSaved(submission, holder.itemView, mContext); - } - }); - } - - AnimatorUtil.setFlashAnimation( - holder.itemView, - holder.save, - ContextCompat.getColor(mContext, R.color.md_amber_500)); - } else { - s = - Snackbar.make( - holder.itemView, - R.string.submission_info_unsaved, - Snackbar.LENGTH_SHORT); - final int getTintColor = - holder.itemView.getTag(holder.itemView.getId()) != null - && holder.itemView - .getTag(holder.itemView.getId()) - .equals("none") - || full - ? Palette.getCurrentTintColor(mContext) - : Palette.getWhiteTintColor(); - BlendModeUtil.tintImageViewAsSrcAtop((ImageView) holder.save, getTintColor); - holder.save.setContentDescription(mContext.getString(R.string.btn_save)); - } - LayoutUtils.showSnackbar(s); - } catch (Exception ignored) { - - } - } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } - - private void categorizeSaved( - final Submission submission, View itemView, final Context mContext) { - new AsyncTask>() { - - Dialog d; - - @Override - public void onPreExecute() { - d = - new MaterialDialog.Builder(mContext) - .progress(true, 100) - .title(R.string.profile_category_loading) - .content(R.string.misc_please_wait) - .show(); - } - - @Override - protected List doInBackground(Void... params) { - try { - List categories = - new ArrayList( - new AccountManager(Authentication.reddit).getSavedCategories()); - categories.add("New category"); - return categories; - } catch (Exception e) { - e.printStackTrace(); - return new ArrayList() { - { - add("New category"); - } - }; - // sub probably has no flairs? - } - } - - @Override - public void onPostExecute(final List data) { - try { - new MaterialDialog.Builder(mContext) - .items(data) - .title(R.string.sidebar_select_flair) - .itemsCallback( - new MaterialDialog.ListCallback() { - @Override - public void onSelection( - MaterialDialog dialog, - final View itemView, - int which, - CharSequence text) { - final String t = data.get(which); - if (which == data.size() - 1) { - new MaterialDialog.Builder(mContext) - .title(R.string.category_set_name) - .input( - mContext.getString( - R.string - .category_set_name_hint), - null, - false, - (dialog1, input) -> {}) - .positiveText(R.string.btn_set) - .onPositive( - new MaterialDialog - .SingleButtonCallback() { - @Override - public void onClick( - MaterialDialog dialog, - DialogAction which) { - final String flair = - dialog.getInputEditText() - .getText() - .toString(); - new AsyncTask< - Void, - Void, - Boolean>() { - @Override - protected Boolean - doInBackground( - Void... - params) { - try { - new AccountManager( - Authentication - .reddit) - .save( - submission, - flair); - return true; - } catch ( - ApiException - e) { - e - .printStackTrace(); - return false; - } - } - - @Override - protected void - onPostExecute( - Boolean - done) { - Snackbar s; - if (done) { - if (itemView - != null) { - s = - Snackbar - .make( - itemView, - R - .string - .submission_info_saved, - Snackbar - .LENGTH_SHORT); - LayoutUtils - .showSnackbar( - s); - } - } else { - if (itemView - != null) { - s = - Snackbar - .make( - itemView, - R - .string - .category_set_error, - Snackbar - .LENGTH_SHORT); - LayoutUtils - .showSnackbar( - s); - } - } - } - }.executeOnExecutor( - AsyncTask - .THREAD_POOL_EXECUTOR); - } - }) - .negativeText(R.string.btn_cancel) - .show(); - } else { - new AsyncTask() { - @Override - protected Boolean doInBackground( - Void... params) { - try { - new AccountManager( - Authentication.reddit) - .save(submission, t); - return true; - } catch (ApiException e) { - e.printStackTrace(); - return false; - } - } - - @Override - protected void onPostExecute(Boolean done) { - Snackbar s; - if (done) { - if (itemView != null) { - s = - Snackbar.make( - itemView, - R.string - .submission_info_saved, - Snackbar - .LENGTH_SHORT); - LayoutUtils.showSnackbar(s); - } - } else { - if (itemView != null) { - s = - Snackbar.make( - itemView, - R.string - .category_set_error, - Snackbar - .LENGTH_SHORT); - LayoutUtils.showSnackbar(s); - } - } - } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } - } - }) - .show(); - if (d != null) { - d.dismiss(); - } - } catch (Exception ignored) { - - } - } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } - - public void hideSubmission( - final Submission submission, - final List posts, - final String baseSub, - final RecyclerView recyclerview, - Context c) { - final int pos = posts.indexOf(submission); - if (pos != -1) { - if (submission.isHidden()) { - posts.remove(pos); - Hidden.undoHidden(submission); - recyclerview.getAdapter().notifyItemRemoved(pos + 1); - Snackbar snack = - Snackbar.make( - recyclerview, - R.string.submission_info_unhidden, - Snackbar.LENGTH_LONG); - LayoutUtils.showSnackbar(snack); - } else { - final T t = posts.get(pos); - posts.remove(pos); - Hidden.setHidden(t); - final OfflineSubreddit s; - boolean success = false; - if (baseSub != null) { - s = OfflineSubreddit.getSubreddit(baseSub, false, c); - try { - s.hide(pos); - success = true; - } catch (Exception e) { - } - } else { - success = false; - s = null; - } - - recyclerview.getAdapter().notifyItemRemoved(pos + 1); - - final boolean finalSuccess = success; - Snackbar snack = - Snackbar.make( - recyclerview, - R.string.submission_info_hidden, - Snackbar.LENGTH_LONG) - .setAction( - R.string.btn_undo, - new View.OnClickListener() { - @Override - public void onClick(View v) { - if (baseSub != null && s != null && finalSuccess) { - s.unhideLast(); - } - posts.add(pos, t); - recyclerview - .getAdapter() - .notifyItemInserted(pos + 1); - Hidden.undoHidden(t); - } - }); - LayoutUtils.showSnackbar(snack); - } - } - } public void showModBottomSheet( final Activity mContext, @@ -1508,7 +429,7 @@ private void doRemoveSubmissionReason( final List posts, final RecyclerView recyclerview, final SubmissionViewHolder holder) { - reason = ""; + SubmissionBottomSheetActions.reason = ""; new MaterialDialog.Builder(mContext) .title(R.string.mod_remove_title) .positiveText(R.string.btn_remove) @@ -1520,7 +441,7 @@ private void doRemoveSubmissionReason( new MaterialDialog.InputCallback() { @Override public void onInput(MaterialDialog dialog, CharSequence input) { - reason = input.toString(); + SubmissionBottomSheetActions.reason = input.toString(); } }) .inputType(InputType.TYPE_TEXT_FLAG_CAP_SENTENCES) @@ -1531,7 +452,7 @@ public void onInput(MaterialDialog dialog, CharSequence input) { public void onClick(final MaterialDialog dialog, DialogAction which) { removeSubmissionReason( - submission, mContext, posts, reason, holder, recyclerview); + submission, mContext, posts, SubmissionBottomSheetActions.reason, holder, recyclerview); } }) .negativeText(R.string.btn_cancel) @@ -2520,7 +1441,7 @@ public void onClick(View v) { new View.OnClickListener() { @Override public void onClick(View view) { - showBottomSheet( + SubmissionBottomSheetActions.showBottomSheet( mContext, submission, holder, posts, baseSub, recyclerview, full); } }); @@ -2672,7 +1593,7 @@ public void onClick(View view) { new View.OnClickListener() { @Override public void onClick(View v) { - hideSubmission(submission, posts, baseSub, recyclerview, mContext); + SubmissionBottomSheetActions.hideSubmission(submission, posts, baseSub, recyclerview, mContext); } }); } else { @@ -2701,7 +1622,7 @@ public void onClick(View v) { new View.OnClickListener() { @Override public void onClick(View v) { - saveSubmission(submission, mContext, holder, full); + SubmissionBottomSheetActions.saveSubmission(submission, mContext, holder, full); } }); } @@ -3817,37 +2738,4 @@ private void setViews(String rawHTML, String subredditName, SubmissionViewHolder } } - public static class AsyncReportTask extends AsyncTask { - private Submission submission; - private View contextView; - - public AsyncReportTask(Submission submission, View contextView) { - this.submission = submission; - this.contextView = contextView; - } - - @Override - protected Void doInBackground(String... reason) { - try { - new AccountManager(Authentication.reddit).report(submission, reason[0]); - } catch (ApiException e) { - e.printStackTrace(); - } - return null; - } - - @Override - protected void onPostExecute(Void aVoid) { - if (contextView != null) { - try { - Snackbar s = - Snackbar.make( - contextView, R.string.msg_report_sent, Snackbar.LENGTH_SHORT); - LayoutUtils.showSnackbar(s); - } catch (Exception ignored) { - - } - } - } - } } \ No newline at end of file diff --git a/app/src/main/java/me/edgan/redditslide/util/SubmissionBottomSheetActions.java b/app/src/main/java/me/edgan/redditslide/util/SubmissionBottomSheetActions.java new file mode 100644 index 000000000..122d13463 --- /dev/null +++ b/app/src/main/java/me/edgan/redditslide/util/SubmissionBottomSheetActions.java @@ -0,0 +1,779 @@ +package me.edgan.redditslide.util; +import android.app.Activity; +import android.app.Dialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.res.TypedArray; +import android.graphics.Color; +import android.graphics.drawable.Drawable; +import android.os.AsyncTask; +import android.view.View; +import android.view.WindowManager; +import android.widget.EditText; +import android.widget.ImageView; +import android.widget.RadioButton; +import android.widget.RadioGroup; +import android.widget.TextView; +import android.widget.Toast; + +import androidx.appcompat.app.AlertDialog; +import androidx.core.content.res.ResourcesCompat; +import androidx.recyclerview.widget.RecyclerView; + +import com.afollestad.materialdialogs.DialogAction; +import com.afollestad.materialdialogs.MaterialDialog; +import com.cocosw.bottomsheet.BottomSheet; +import com.google.android.material.snackbar.Snackbar; + +import net.dean.jraw.ApiException; +import net.dean.jraw.models.Contribution; +import net.dean.jraw.models.Ruleset; +import net.dean.jraw.models.Submission; +import net.dean.jraw.models.SubredditRule; + +import org.apache.commons.text.StringEscapeUtils; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Locale; + +import me.edgan.redditslide.ActionStates; +import me.edgan.redditslide.Activities.PostReadLater; +import me.edgan.redditslide.Activities.Profile; +import me.edgan.redditslide.Activities.SubredditView; +import me.edgan.redditslide.Adapters.SubmissionViewHolder; +import me.edgan.redditslide.Authentication; +import me.edgan.redditslide.CommentCacheAsync; +import me.edgan.redditslide.Hidden; +import me.edgan.redditslide.OfflineSubreddit; +import me.edgan.redditslide.PostMatch; +import me.edgan.redditslide.R; +import me.edgan.redditslide.SubmissionViews.ReadLater; +import me.edgan.redditslide.Reddit; +import me.edgan.redditslide.SettingValues; +import me.edgan.redditslide.Visuals.Palette; + +/** + * Handles Bottom Sheet actions for Submission views. + */ +public class SubmissionBottomSheetActions { + + public static String reason; + public static boolean[] chosen = new boolean[] {false, false, false, false, false}; + public static boolean[] oldChosen = new boolean[] {false, false, false, false, false}; + + + public static void showBottomSheet( + final Activity mContext, + final Submission submission, + final SubmissionViewHolder holder, + final List posts, + final String baseSub, + final RecyclerView recyclerview, + final boolean full) { + + int[] attrs = new int[] {R.attr.tintColor}; + TypedArray ta = mContext.obtainStyledAttributes(attrs); + + int color = ta.getColor(0, Color.WHITE); + Drawable profile = ResourcesCompat.getDrawable(mContext.getResources(), R.drawable.ic_account_circle, null); + final Drawable sub = ResourcesCompat.getDrawable(mContext.getResources(), R.drawable.ic_bookmark_border, null); + Drawable saved = ResourcesCompat.getDrawable(mContext.getResources(), R.drawable.ic_star, null); + Drawable hide = ResourcesCompat.getDrawable(mContext.getResources(), R.drawable.ic_visibility_off, null); + final Drawable report = ResourcesCompat.getDrawable(mContext.getResources(), R.drawable.ic_report, null); + Drawable copy = ResourcesCompat.getDrawable(mContext.getResources(), R.drawable.ic_content_copy, null); + final Drawable readLater = ResourcesCompat.getDrawable(mContext.getResources(), R.drawable.ic_download, null); + Drawable open = ResourcesCompat.getDrawable(mContext.getResources(), R.drawable.ic_open_in_browser, null); + Drawable link = ResourcesCompat.getDrawable(mContext.getResources(), R.drawable.ic_link, null); + Drawable reddit = ResourcesCompat.getDrawable(mContext.getResources(), R.drawable.ic_forum, null); + Drawable filter = ResourcesCompat.getDrawable(mContext.getResources(), R.drawable.ic_filter_list, null); + Drawable crosspost = ResourcesCompat.getDrawable(mContext.getResources(), R.drawable.ic_forward, null); + + final List drawableSet = Arrays.asList(profile, sub, saved, hide, report, copy, open, link, reddit, readLater, filter, crosspost); + BlendModeUtil.tintDrawablesAsSrcAtop(drawableSet, color); + + ta.recycle(); + + final BottomSheet.Builder b = new BottomSheet.Builder(mContext).title(CompatUtil.fromHtml(submission.getTitle())); + + final boolean isReadLater = mContext instanceof PostReadLater; + final boolean isAddedToReadLaterList = ReadLater.isToBeReadLater(submission); + if (Authentication.didOnline) { + b.sheet(1, profile, "/u/" + submission.getAuthor()).sheet(2, sub, "/r/" + submission.getSubredditName()); + String save = mContext.getString(R.string.btn_save); + if (ActionStates.isSaved(submission)) { + save = mContext.getString(R.string.comment_unsave); + } + if (Authentication.isLoggedIn) { + b.sheet(3, saved, save); + } + } + + if (isAddedToReadLaterList) { + CharSequence markAsReadCs = mContext.getString(R.string.mark_as_read); + b.sheet(28, readLater, markAsReadCs); + } else { + CharSequence readLaterCs = mContext.getString(R.string.read_later); + b.sheet(28, readLater, readLaterCs); + } + + if (Authentication.didOnline) { + if (Authentication.isLoggedIn) { + b.sheet(12, report, mContext.getString(R.string.btn_report)); + b.sheet(13, crosspost, mContext.getString(R.string.btn_crosspost)); + } + } + + if (submission.getSelftext() != null && !submission.getSelftext().isEmpty() && full) { + b.sheet(25, copy, mContext.getString(R.string.submission_copy_text)); + } + + boolean hidden = submission.isHidden(); + if (!full && Authentication.didOnline) { + if (!hidden) { + b.sheet(5, hide, mContext.getString(R.string.submission_hide)); + } else { + b.sheet(5, hide, mContext.getString(R.string.submission_unhide)); + } + } + b.sheet(7, open, mContext.getString(R.string.open_externally)); + b.sheet(4, link, mContext.getString(R.string.submission_share_permalink)).sheet(8, reddit, mContext.getString(R.string.submission_share_reddit_url)); + if ((mContext instanceof me.edgan.redditslide.Activities.MainActivity) || (mContext instanceof SubredditView)) { + b.sheet(10, filter, mContext.getString(R.string.filter_content)); + } + + b.listener(new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + switch (which) { + case 1: + { + Intent i = new Intent(mContext, Profile.class); + i.putExtra(Profile.EXTRA_PROFILE, submission.getAuthor()); + mContext.startActivity(i); + } + + break; + case 2: + { + Intent i = new Intent(mContext, SubredditView.class); + i.putExtra(SubredditView.EXTRA_SUBREDDIT, submission.getSubredditName()); + mContext.startActivityForResult(i, 14); + } + + break; + case 10: + String[] choices; + final String flair = submission.getSubmissionFlair().getText() != null ? submission.getSubmissionFlair().getText() : ""; + + if (flair.isEmpty()) { + choices = new String[] { + mContext.getString(R.string.filter_posts_sub, submission.getSubredditName()), + mContext.getString(R.string.filter_posts_user, submission.getAuthor()), + mContext.getString(R.string.filter_posts_urls, submission.getDomain()), + mContext.getString(R.string.filter_open_externally, submission.getDomain()) + }; + + chosen = new boolean[] { + SettingValues.subredditFilters.contains(submission.getSubredditName().toLowerCase(Locale.ENGLISH)), + SettingValues.userFilters.contains(submission.getAuthor().toLowerCase(Locale.ENGLISH)), + SettingValues.domainFilters.contains(submission.getDomain().toLowerCase(Locale.ENGLISH)), + SettingValues.alwaysExternal.contains(submission.getDomain().toLowerCase(Locale.ENGLISH)), + false // Placeholder for flair filter + }; + + oldChosen = chosen.clone(); + } else { + choices = new String[] { + mContext.getString(R.string.filter_posts_sub, submission.getSubredditName()), + mContext.getString(R.string.filter_posts_user, submission.getAuthor()), + mContext.getString(R.string.filter_posts_urls, submission.getDomain()), + mContext.getString(R.string.filter_open_externally, submission.getDomain()), + mContext.getString(R.string.filter_posts_flair, flair, baseSub) + }; + + chosen = new boolean[] { + SettingValues.subredditFilters.contains(submission.getSubredditName().toLowerCase(Locale.ENGLISH)), + SettingValues.userFilters.contains(submission.getAuthor().toLowerCase(Locale.ENGLISH)), + SettingValues.domainFilters.contains(submission.getDomain().toLowerCase(Locale.ENGLISH)), + SettingValues.alwaysExternal.contains(submission.getDomain().toLowerCase(Locale.ENGLISH)), + SettingValues.flairFilters.contains(baseSub + ":" + flair.toLowerCase(Locale.ENGLISH).trim()) + }; + + oldChosen = chosen.clone(); + } + + + new AlertDialog.Builder(mContext) + .setTitle(R.string.filter_title) + .setMultiChoiceItems(choices, chosen, (dialog1, which1, isChecked) -> chosen[which1] = isChecked) + .setPositiveButton(R.string.filter_btn, (dialog12, which12) -> { + boolean filtered = false; + SharedPreferences.Editor e = SettingValues.prefs.edit(); + if (chosen[0] && chosen[0] != oldChosen[0]) { + SettingValues.subredditFilters.add(submission.getSubredditName().toLowerCase(Locale.ENGLISH).trim()); + filtered = true; + e.putStringSet(SettingValues.PREF_SUBREDDIT_FILTERS, SettingValues.subredditFilters); + } else if (!chosen[0] && chosen[0] != oldChosen[0]) { + SettingValues.subredditFilters.remove(submission.getSubredditName().toLowerCase(Locale.ENGLISH).trim()); + filtered = false; + e.putStringSet(SettingValues.PREF_SUBREDDIT_FILTERS, SettingValues.subredditFilters); + e.apply(); + } + + if (chosen[1] && chosen[1] != oldChosen[1]) { + SettingValues.userFilters.add(submission.getAuthor().toLowerCase(Locale.ENGLISH).trim()); + filtered = true; + e.putStringSet(SettingValues.PREF_USER_FILTERS, SettingValues.userFilters); + } else if (!chosen[1] && chosen[1] != oldChosen[1]) { + SettingValues.userFilters.remove(submission.getAuthor().toLowerCase(Locale.ENGLISH).trim()); + filtered = false; + e.putStringSet(SettingValues.PREF_USER_FILTERS, SettingValues.userFilters); + e.apply(); + } + + if (chosen[2] && chosen[2] != oldChosen[2]) { + SettingValues.domainFilters.add(submission.getDomain().toLowerCase(Locale.ENGLISH).trim()); + filtered = true; + e.putStringSet(SettingValues.PREF_DOMAIN_FILTERS, SettingValues.domainFilters); + } else if (!chosen[2] && chosen[2] != oldChosen[2]) { + SettingValues.domainFilters.remove(submission.getDomain().toLowerCase(Locale.ENGLISH).trim()); + filtered = false; + e.putStringSet(SettingValues.PREF_DOMAIN_FILTERS, SettingValues.domainFilters); + e.apply(); + } + + if (chosen[3] && chosen[3] != oldChosen[3]) { + SettingValues.alwaysExternal.add(submission.getDomain().toLowerCase(Locale.ENGLISH).trim()); + e.putStringSet(SettingValues.PREF_ALWAYS_EXTERNAL, SettingValues.alwaysExternal); + e.apply(); + } else if (!chosen[3] && chosen[3] != oldChosen[3]) { + SettingValues.alwaysExternal.remove(submission.getDomain().toLowerCase(Locale.ENGLISH).trim()); + e.putStringSet(SettingValues.PREF_ALWAYS_EXTERNAL, SettingValues.alwaysExternal); + e.apply(); + } + + if (chosen.length > 4 && !flair.isEmpty()) { + String s = (baseSub + ":" + flair).toLowerCase(Locale.ENGLISH).trim(); + + if (chosen[4] && chosen[4] != oldChosen[4]) { + SettingValues.flairFilters.add(s); + e.putStringSet(SettingValues.PREF_FLAIR_FILTERS, SettingValues.flairFilters); + e.apply(); + filtered = true; + } else if (!chosen[4] && chosen[4] != oldChosen[4]) { + SettingValues.flairFilters.remove(s); + e.putStringSet(SettingValues.PREF_FLAIR_FILTERS, SettingValues.flairFilters); + e.apply(); + } + } + + if (filtered) { + e.apply(); + ArrayList toRemove = new ArrayList<>(); + + for (Contribution s : posts) { + if (s instanceof Submission && PostMatch.doesMatch((Submission) s)) { + toRemove.add(s); + } + } + + OfflineSubreddit s = OfflineSubreddit.getSubreddit(baseSub, false, mContext); + + for (Contribution remove : toRemove) { + final int pos = posts.indexOf(remove); + posts.remove(pos); + if (baseSub != null) { + s.hideMulti(pos); + } + } + + s.writeToMemoryNoStorage(); + recyclerview.getAdapter().notifyDataSetChanged(); + } + }).setNegativeButton(R.string.btn_cancel, null).show(); + + break; + case 3: + saveSubmission(submission, mContext, holder, full); + + break; + case 5: + hideSubmission(submission, posts, baseSub, recyclerview, mContext); + + break; + case 7: + LinkUtil.openExternally(submission.getUrl()); + + if (submission.isNsfw() && !SettingValues.storeNSFWHistory) { + // Do nothing if the post is NSFW and storeNSFWHistory is not enabled + } else if (SettingValues.storeHistory) { + me.edgan.redditslide.HasSeen.addSeen(submission.getFullName()); + } + + break; + case 13: + LinkUtil.crosspost(submission, mContext); + + break; + case 28: + if (!isAddedToReadLaterList) { + ReadLater.setReadLater(submission, true); + Snackbar s = Snackbar.make(holder.itemView, "Added to read later!", Snackbar.LENGTH_SHORT); + View view = s.getView(); + TextView tv = view.findViewById(com.google.android.material.R.id.snackbar_text); + tv.setTextColor(Color.WHITE); + s.setAction( + R.string.btn_undo, + new View.OnClickListener() { + @Override + public void onClick(View view) { + ReadLater.setReadLater(submission, false); + Snackbar s2 = Snackbar.make(holder.itemView, "Removed from read later", Snackbar.LENGTH_SHORT); + LayoutUtils.showSnackbar(s2); + } + } + ); + + if (NetworkUtil.isConnected(mContext)) { + new CommentCacheAsync( + Collections.singletonList(submission), + mContext, + CommentCacheAsync.SAVED_SUBMISSIONS, + new boolean[] {true, true} + ).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + s.show(); + } else { + ReadLater.setReadLater(submission, false); + if (isReadLater || !Authentication.didOnline) { + final int pos = posts.indexOf(submission); + posts.remove(submission); + + recyclerview.getAdapter().notifyItemRemoved(holder.getBindingAdapterPosition()); + + Snackbar s2 = Snackbar.make(holder.itemView, "Removed from read later", Snackbar.LENGTH_SHORT); + View view2 = s2.getView(); + TextView tv2 = view2.findViewById(com.google.android.material.R.id.snackbar_text); + tv2.setTextColor(Color.WHITE); + s2.setAction( + R.string.btn_undo, + new View.OnClickListener() { + @Override + public void onClick(View view) { + posts.add(pos, (T) submission); + recyclerview.getAdapter().notifyDataSetChanged(); + } + } + ); + } else { + Snackbar s2 = Snackbar.make(holder.itemView, "Removed from read later", Snackbar.LENGTH_SHORT); + View view2 = s2.getView(); + TextView tv2 = view2.findViewById(com.google.android.material.R.id.snackbar_text); + s2.show(); + } + OfflineSubreddit.newSubreddit(CommentCacheAsync.SAVED_SUBMISSIONS).deleteFromMemory(submission.getFullName()); + } + + break; + case 4: + Reddit.defaultShareText(CompatUtil.fromHtml(submission.getTitle()).toString(), StringEscapeUtils.escapeHtml4(submission.getUrl()), mContext); + + break; + case 12: + final MaterialDialog reportDialog = + new MaterialDialog.Builder(mContext) + .customView(R.layout.report_dialog, true) + .title(R.string.report_post) + .positiveText(R.string.btn_report) + .negativeText(R.string.btn_cancel) + .onPositive( + new MaterialDialog.SingleButtonCallback() { + @Override + public void onClick(MaterialDialog dialog, DialogAction which) { + RadioGroup reasonGroup = dialog.getCustomView().findViewById(R.id.report_reasons); + String reportReason; + if (reasonGroup.getCheckedRadioButtonId() == R.id.report_other) { + reportReason = ((EditText) dialog.getCustomView().findViewById(R.id.input_report_reason)).getText().toString(); + } else { + reportReason = ((RadioButton) reasonGroup.findViewById(reasonGroup.getCheckedRadioButtonId())).getText().toString(); + } + + new AsyncReportTask(submission, holder.itemView).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, reportReason); + } + } + ).build(); + + final RadioGroup reasonGroup = reportDialog.getCustomView().findViewById(R.id.report_reasons); + + reasonGroup.setOnCheckedChangeListener( + new RadioGroup.OnCheckedChangeListener() { + @Override + public void onCheckedChanged(RadioGroup group, int checkedId) { + if (checkedId == R.id.report_other) { + reportDialog.getCustomView().findViewById(R.id.input_report_reason).setVisibility(View.VISIBLE); + } else { + reportDialog.getCustomView().findViewById(R.id.input_report_reason).setVisibility(View.GONE); + } + } + } + ); + + // Load sub's report reasons and show the appropriate ones + new AsyncTask() { + @Override + protected Ruleset doInBackground(Void... voids) { + return Authentication.reddit.getRules( + submission.getSubredditName()); + } + + @Override + protected void onPostExecute(Ruleset rules) { + reportDialog.getCustomView().findViewById(R.id.report_loading).setVisibility(View.GONE); + if (rules.getSubredditRules().size() > 0) { + TextView subHeader = new TextView(mContext); + subHeader.setText(mContext.getString(R.string.report_sub_rules, submission.getSubredditName())); + reasonGroup.addView(subHeader, reasonGroup.getChildCount() - 2); + } + + for (SubredditRule rule : rules.getSubredditRules()) { + if (rule.getKind() == SubredditRule.RuleKind.LINK || rule.getKind() == SubredditRule.RuleKind.ALL) { + RadioButton btn = new RadioButton(mContext); + btn.setText(rule.getViolationReason()); + reasonGroup.addView(btn, reasonGroup.getChildCount() - 2); + btn.getLayoutParams().width = WindowManager.LayoutParams.MATCH_PARENT; + } + } + + if (rules.getSiteRules().size() > 0) { + TextView siteHeader = new TextView(mContext); + siteHeader.setText(R.string.report_site_rules); + reasonGroup.addView(siteHeader, reasonGroup.getChildCount() - 2); + } + + for (String rule : rules.getSiteRules()) { + RadioButton btn = new RadioButton(mContext); + btn.setText(rule); + reasonGroup.addView(btn, reasonGroup.getChildCount() - 2); + btn.getLayoutParams().width = WindowManager.LayoutParams.MATCH_PARENT; + } + } + }.execute(); + + reportDialog.show(); + + break; + case 8: + if (SettingValues.shareLongLink) { + Reddit.defaultShareText(submission.getTitle(), "https://reddit.com" + submission.getPermalink(), mContext); + } else { + Reddit.defaultShareText(submission.getTitle(), "https://reddit.com/comments/" + submission.getId(), mContext); + } + + break; + case 6: + ClipboardUtil.copyToClipboard(mContext, "Link", submission.getUrl()); + Toast.makeText(mContext, R.string.submission_link_copied, Toast.LENGTH_SHORT).show(); + + break; + case 25: + final TextView showText = new TextView(mContext); + showText.setText(StringEscapeUtils.unescapeHtml4(submission.getTitle() + "\n\n" + submission.getSelftext())); + showText.setTextIsSelectable(true); + int sixteen = DisplayUtil.dpToPxVertical(24); + showText.setPadding(sixteen, 0, sixteen, 0); + new AlertDialog.Builder(mContext) + .setView(showText) + .setTitle("Select text to copy") + .setCancelable(true) + .setPositiveButton( + "COPY SELECTED", + (dialog13, which13) -> { + String selected = showText.getText().toString().substring(showText.getSelectionStart(), showText.getSelectionEnd()); + if (!selected.isEmpty()) { + ClipboardUtil.copyToClipboard(mContext, "Selftext", selected); + } else { + ClipboardUtil.copyToClipboard(mContext, "Selftext", CompatUtil.fromHtml(submission.getTitle() + "\n\n" + submission.getSelftext())); + } + Toast.makeText(mContext, R.string.submission_comment_copied, Toast.LENGTH_SHORT).show(); + }) + .setNegativeButton(R.string.btn_cancel, null) + .setNeutralButton("COPY ALL", (dialog14, which14) -> { + ClipboardUtil.copyToClipboard(mContext, "Selftext", StringEscapeUtils.unescapeHtml4(submission.getTitle() + "\n\n" + submission.getSelftext())); + Toast.makeText(mContext, R.string.submission_text_copied, Toast.LENGTH_SHORT).show(); + }).show(); + + break; + } + } + }); + b.show(); + } + + public static void saveSubmission(final Submission submission, final Activity mContext, final SubmissionViewHolder holder, final boolean full) { + new AsyncTask() { + @Override + protected Void doInBackground(Void... params) { + try { + if (ActionStates.isSaved(submission)) { + new net.dean.jraw.managers.AccountManager(Authentication.reddit).unsave(submission); + ActionStates.setSaved(submission, false); + } else { + new net.dean.jraw.managers.AccountManager(Authentication.reddit).save(submission); + ActionStates.setSaved(submission, true); + } + + } catch (Exception e) { + e.printStackTrace(); + } + + return null; + } + + @Override + protected void onPostExecute(Void aVoid) { + Snackbar s; + try { + if (ActionStates.isSaved(submission)) { + BlendModeUtil.tintImageViewAsSrcAtop((ImageView) holder.save, Palette.getCurrentTintColor(mContext)); + holder.save.setContentDescription(mContext.getString(R.string.btn_unsave)); + s = Snackbar.make(holder.itemView, R.string.submission_info_saved, Snackbar.LENGTH_LONG); + if (Authentication.me.hasGold()) { + s.setAction( + R.string.category_categorize, + new View.OnClickListener() { + @Override + public void onClick(View v) { + categorizeSaved(submission, holder.itemView, mContext); + } + }); + } + + AnimatorUtil.setFlashAnimation(holder.itemView, holder.save, Palette.getCurrentTintColor(mContext)); + } else { + s = Snackbar.make(holder.itemView, R.string.submission_info_unsaved, Snackbar.LENGTH_SHORT); + final int getTintColor = + holder.itemView.getTag(holder.itemView.getId()) != null + && holder.itemView + .getTag(holder.itemView.getId()) + .equals("none") + || full + ? Palette.getCurrentTintColor(mContext) + : Palette.getWhiteTintColor(); + BlendModeUtil.tintImageViewAsSrcAtop((ImageView) holder.save, getTintColor); + holder.save.setContentDescription(mContext.getString(R.string.btn_save)); + } + LayoutUtils.showSnackbar(s); + } catch (Exception ignored) { + + } + } + }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + public static void categorizeSaved( + final Submission submission, View itemView, final Context mContext) { + new AsyncTask>() { + + Dialog d; + + @Override + public void onPreExecute() { + d = new MaterialDialog.Builder(mContext).progress(true, 100).title(R.string.profile_category_loading).content(R.string.misc_please_wait).show(); + } + + @Override + protected List doInBackground(Void... params) { + try { + List categories = new ArrayList(new net.dean.jraw.managers.AccountManager(Authentication.reddit).getSavedCategories()); + categories.add("New category"); + return categories; + } catch (Exception e) { + e.printStackTrace(); + return new ArrayList() { + { + add("New category"); + } + }; + // sub probably has no flairs? + } + } + + @Override + public void onPostExecute(final List data) { + try { + new MaterialDialog.Builder(mContext).items(data).title(R.string.sidebar_select_flair).itemsCallback(new MaterialDialog.ListCallback() { + @Override + public void onSelection(MaterialDialog dialog, final View itemView, int which, CharSequence text) { + final String t = data.get(which); + if (which == data.size() - 1) { + new MaterialDialog.Builder(mContext) + .title(R.string.category_set_name) + .input(mContext.getString(R.string.category_set_name_hint), null, false, (dialog1, input) -> {}) + .positiveText(R.string.btn_set) + .onPositive( + new MaterialDialog.SingleButtonCallback() { + @Override + public void onClick(MaterialDialog dialog, DialogAction which) { + final String flair = dialog.getInputEditText().getText().toString(); + new AsyncTask() { + @Override + protected Boolean doInBackground(Void... params) { + try { + new net.dean.jraw.managers.AccountManager(Authentication.reddit).save(submission, flair); + return true; + } catch (ApiException e) { + e.printStackTrace(); + + return false; + } + } + + @Override + protected void onPostExecute(Boolean done) { + Snackbar s; + if (done) { + if (itemView != null) { + s = Snackbar.make(itemView, R.string.submission_info_saved, Snackbar.LENGTH_SHORT); + LayoutUtils.showSnackbar(s); + } + } else { + if (itemView != null) { + s = Snackbar.make(itemView, R.string.category_set_error, Snackbar.LENGTH_SHORT); + LayoutUtils.showSnackbar(s); + } + } + } + }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + }).negativeText(R.string.btn_cancel).show(); + } else { + new AsyncTask() { + @Override + protected Boolean doInBackground(Void... params) { + try { + new net.dean.jraw.managers.AccountManager(Authentication.reddit).save(submission, t); + + return true; + } catch (ApiException e) { + e.printStackTrace(); + + return false; + } + } + + @Override + protected void onPostExecute(Boolean done) { + Snackbar s; + if (done) { + if (itemView != null) { + s = Snackbar.make(itemView, R.string.submission_info_saved, Snackbar.LENGTH_SHORT); + LayoutUtils.showSnackbar(s); + } + } else { + if (itemView != null) { + s = Snackbar.make(itemView, R.string.category_set_error, Snackbar.LENGTH_SHORT); + LayoutUtils.showSnackbar(s); + } + } + } + }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + } + }).show(); + + if (d != null) { + d.dismiss(); + } + } catch (Exception ignored) {} + } + }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + public static void hideSubmission(final Submission submission, final List posts, final String baseSub, final RecyclerView recyclerview, Context c) { + final int pos = posts.indexOf(submission); + if (pos != -1) { + if (submission.isHidden()) { + posts.remove(pos); + Hidden.undoHidden(submission); + recyclerview.getAdapter().notifyItemRemoved(pos + 1); + Snackbar snack = Snackbar.make(recyclerview, R.string.submission_info_unhidden, Snackbar.LENGTH_LONG); + LayoutUtils.showSnackbar(snack); + } else { + final T t = posts.get(pos); + posts.remove(pos); + Hidden.setHidden(t); + final OfflineSubreddit s; + boolean success = false; + if (baseSub != null) { + s = OfflineSubreddit.getSubreddit(baseSub, false, c); + try { + s.hide(pos); + success = true; + } catch (Exception e) {} + } else { + success = false; + s = null; + } + + recyclerview.getAdapter().notifyItemRemoved(pos + 1); + + final boolean finalSuccess = success; + Snackbar snack = Snackbar.make(recyclerview, R.string.submission_info_hidden, Snackbar.LENGTH_LONG) + .setAction( + R.string.btn_undo, + new View.OnClickListener() { + @Override + public void onClick(View v) { + if (baseSub != null && s != null && finalSuccess) { + s.unhideLast(); + } + posts.add(pos, t); + recyclerview.getAdapter().notifyItemInserted(pos + 1); + Hidden.undoHidden(t); + } + } + ); + LayoutUtils.showSnackbar(snack); + } + } + } + + public static class AsyncReportTask extends AsyncTask { + private Submission submission; + private View contextView; + + public AsyncReportTask(Submission submission, View contextView) { + this.submission = submission; + this.contextView = contextView; + } + + @Override + protected Void doInBackground(String... reason) { + try { + new net.dean.jraw.managers.AccountManager(Authentication.reddit).report(submission, reason[0]); + } catch (ApiException e) { + e.printStackTrace(); + } + return null; + } + + @Override + protected void onPostExecute(Void aVoid) { + if (contextView != null) { + try { + Snackbar s = Snackbar.make(contextView, R.string.msg_report_sent, Snackbar.LENGTH_SHORT); + Snackbar.make(contextView, R.string.msg_report_sent, Snackbar.LENGTH_SHORT); + LayoutUtils.showSnackbar(s); + } catch (Exception ignored) { + + } + } + } + } +} \ No newline at end of file From e805bc20185e0d40422d971fea8b2fa9aa0a12f1 Mon Sep 17 00:00:00 2001 From: Nathan Grennan Date: Mon, 31 Mar 2025 22:39:25 -0700 Subject: [PATCH 13/99] Broke out SubmissionModActions.java from PopulateSubmissionViewHolder.java --- .../PopulateSubmissionViewHolder.java | 1313 +---------------- .../util/SubmissionModActions.java | 1166 +++++++++++++++ app/src/main/res/values/strings.xml | 12 + 3 files changed, 1185 insertions(+), 1306 deletions(-) create mode 100644 app/src/main/java/me/edgan/redditslide/util/SubmissionModActions.java diff --git a/app/src/main/java/me/edgan/redditslide/SubmissionViews/PopulateSubmissionViewHolder.java b/app/src/main/java/me/edgan/redditslide/SubmissionViews/PopulateSubmissionViewHolder.java index 27ac5c5e2..db5d43adc 100644 --- a/app/src/main/java/me/edgan/redditslide/SubmissionViews/PopulateSubmissionViewHolder.java +++ b/app/src/main/java/me/edgan/redditslide/SubmissionViews/PopulateSubmissionViewHolder.java @@ -3,31 +3,23 @@ import android.app.Activity; import android.app.Dialog; -import android.content.Context; import android.content.DialogInterface; -import android.content.Intent; -import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.Color; import android.graphics.Typeface; import android.graphics.drawable.Drawable; import android.os.AsyncTask; -import android.text.InputType; -import android.text.SpannableStringBuilder; -import android.text.style.AbsoluteSizeSpan; import android.view.LayoutInflater; import android.view.View; import android.view.WindowManager; import android.widget.EditText; import android.widget.ImageView; -import android.widget.LinearLayout; import android.widget.TextView; import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; import androidx.core.content.ContextCompat; -import androidx.core.content.res.ResourcesCompat; import androidx.recyclerview.widget.RecyclerView; import com.afollestad.materialdialogs.DialogAction; @@ -38,9 +30,6 @@ import me.edgan.redditslide.ActionStates; import me.edgan.redditslide.Activities.MainActivity; -import me.edgan.redditslide.Activities.ModQueue; -import me.edgan.redditslide.Activities.Profile; -import me.edgan.redditslide.Activities.Reauthenticate; import me.edgan.redditslide.Adapters.CommentAdapter; import me.edgan.redditslide.Adapters.SubmissionViewHolder; import me.edgan.redditslide.Authentication; @@ -52,7 +41,6 @@ import me.edgan.redditslide.Reddit; import me.edgan.redditslide.SettingValues; import me.edgan.redditslide.SubmissionCache; -import me.edgan.redditslide.Toolbox.ToolboxUI; import me.edgan.redditslide.UserSubscriptions; import me.edgan.redditslide.Views.CreateCardView; import me.edgan.redditslide.Views.DoEditorActions; @@ -62,24 +50,20 @@ import me.edgan.redditslide.util.AnimatorUtil; import me.edgan.redditslide.util.BlendModeUtil; import me.edgan.redditslide.util.CompatUtil; -import me.edgan.redditslide.util.DisplayUtil; import me.edgan.redditslide.util.LayoutUtils; import me.edgan.redditslide.util.OnSingleClickListener; import me.edgan.redditslide.util.SubmissionBottomSheetActions; +import me.edgan.redditslide.util.SubmissionModActions; import me.edgan.redditslide.util.SubmissionParser; import net.dean.jraw.ApiException; import net.dean.jraw.fluent.FlairReference; import net.dean.jraw.fluent.FluentRedditClient; -import net.dean.jraw.http.NetworkException; -import net.dean.jraw.http.oauth.InvalidScopeException; import net.dean.jraw.managers.AccountManager; import net.dean.jraw.managers.ModerationManager; import net.dean.jraw.models.Contribution; -import net.dean.jraw.models.DistinguishedStatus; import net.dean.jraw.models.FlairTemplate; import net.dean.jraw.models.Submission; -import net.dean.jraw.models.Thing; import net.dean.jraw.models.VoteDirection; import org.apache.commons.text.StringEscapeUtils; @@ -98,1289 +82,6 @@ public PopulateSubmissionViewHolder() {} - public void showModBottomSheet( - final Activity mContext, - final Submission submission, - final List posts, - final SubmissionViewHolder holder, - final RecyclerView recyclerview, - final Map reports, - final Map reports2) { - - final Resources res = mContext.getResources(); - int[] attrs = new int[] {R.attr.tintColor}; - TypedArray ta = mContext.obtainStyledAttributes(attrs); - - int color = ta.getColor(0, Color.WHITE); - Drawable profile = - ResourcesCompat.getDrawable( - mContext.getResources(), R.drawable.ic_account_circle, null); - final Drawable report = - ResourcesCompat.getDrawable(mContext.getResources(), R.drawable.ic_report, null); - final Drawable approve = - ResourcesCompat.getDrawable(mContext.getResources(), R.drawable.ic_thumb_up, null); - final Drawable nsfw = - ResourcesCompat.getDrawable( - mContext.getResources(), R.drawable.ic_visibility_off, null); - final Drawable spoiler = - ResourcesCompat.getDrawable( - mContext.getResources(), R.drawable.ic_remove_circle, null); - final Drawable pin = - ResourcesCompat.getDrawable( - mContext.getResources(), R.drawable.ic_bookmark_border, null); - final Drawable lock = - ResourcesCompat.getDrawable(mContext.getResources(), R.drawable.ic_lock, null); - final Drawable flair = - ResourcesCompat.getDrawable( - mContext.getResources(), R.drawable.ic_format_quote, null); - final Drawable remove = - ResourcesCompat.getDrawable(mContext.getResources(), R.drawable.ic_close, null); - final Drawable remove_reason = - ResourcesCompat.getDrawable( - mContext.getResources(), R.drawable.ic_announcement, null); - final Drawable ban = - ResourcesCompat.getDrawable(mContext.getResources(), R.drawable.ic_gavel, null); - final Drawable spam = - ResourcesCompat.getDrawable(mContext.getResources(), R.drawable.ic_flag, null); - final Drawable distinguish = - ResourcesCompat.getDrawable(mContext.getResources(), R.drawable.ic_star, null); - final Drawable note = - ResourcesCompat.getDrawable(mContext.getResources(), R.drawable.ic_note, null); - - final List drawableSet = - Arrays.asList( - profile, - report, - approve, - spam, - nsfw, - pin, - flair, - remove, - spoiler, - remove_reason, - ban, - spam, - distinguish, - lock, - note); - BlendModeUtil.tintDrawablesAsSrcAtop(drawableSet, color); - - ta.recycle(); - - BottomSheet.Builder b = - new BottomSheet.Builder(mContext).title(CompatUtil.fromHtml(submission.getTitle())); - - int reportCount = reports.size() + reports2.size(); - - b.sheet( - 0, - report, - res.getQuantityString(R.plurals.mod_btn_reports, reportCount, reportCount)); - - if (SettingValues.toolboxEnabled) { - b.sheet(24, note, res.getString(R.string.mod_usernotes_view)); - } - - boolean approved = false; - String whoApproved = ""; - b.sheet(1, approve, res.getString(R.string.mod_btn_approve)); - b.sheet(6, remove, mContext.getString(R.string.mod_btn_remove)) - .sheet(7, remove_reason, res.getString(R.string.mod_btn_remove_reason)) - .sheet(30, spam, res.getString(R.string.mod_btn_spam)); - - // b.sheet(2, spam, mContext.getString(R.string.mod_btn_spam)) todo this - b.sheet(20, flair, res.getString(R.string.mod_btn_submission_flair)); - - final boolean isNsfw = submission.isNsfw(); - if (isNsfw) { - b.sheet(3, nsfw, res.getString(R.string.mod_btn_unmark_nsfw)); - } else { - b.sheet(3, nsfw, res.getString(R.string.mod_btn_mark_nsfw)); - } - - final boolean isSpoiler = submission.getDataNode().get("spoiler").asBoolean(); - if (isSpoiler) { - b.sheet(12, nsfw, res.getString(R.string.mod_btn_unmark_spoiler)); - } else { - b.sheet(12, nsfw, res.getString(R.string.mod_btn_mark_spoiler)); - } - - final boolean locked = submission.isLocked(); - if (locked) { - b.sheet(9, lock, res.getString(R.string.mod_btn_unlock_thread)); - } else { - b.sheet(9, lock, res.getString(R.string.mod_btn_lock_thread)); - } - - final boolean stickied = submission.isStickied(); - if (!SubmissionCache.removed.contains(submission.getFullName())) { - if (stickied) { - b.sheet(4, pin, res.getString(R.string.mod_btn_unpin)); - } else { - b.sheet(4, pin, res.getString(R.string.mod_btn_pin)); - } - } - - final boolean distinguished = - submission.getDistinguishedStatus() == DistinguishedStatus.MODERATOR - || submission.getDistinguishedStatus() == DistinguishedStatus.ADMIN; - if (submission.getAuthor().equalsIgnoreCase(Authentication.name)) { - if (distinguished) { - b.sheet(5, distinguish, "Undistingiush"); - } else { - b.sheet(5, distinguish, "Distinguish"); - } - } - - final String finalWhoApproved = whoApproved; - final boolean finalApproved = approved; - b.sheet(8, profile, res.getString(R.string.mod_btn_author)); - b.sheet(23, ban, mContext.getString(R.string.mod_ban_user)); - b.listener( - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - switch (which) { - case 0: - new AsyncTask>() { - @Override - protected ArrayList doInBackground(Void... params) { - - ArrayList finalReports = new ArrayList<>(); - for (Map.Entry entry : - reports.entrySet()) { - finalReports.add( - entry.getValue() + "× " + entry.getKey()); - } - for (Map.Entry entry : - reports2.entrySet()) { - finalReports.add( - entry.getKey() + ": " + entry.getValue()); - } - if (finalReports.isEmpty()) { - finalReports.add( - mContext.getString(R.string.mod_no_reports)); - } - return finalReports; - } - - @Override - public void onPostExecute(ArrayList data) { - new AlertDialog.Builder(mContext) - .setTitle(R.string.mod_reports) - .setItems(data.toArray(new CharSequence[0]), null) - .show(); - } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - - break; - case 1: - if (finalApproved) { - Intent i = new Intent(mContext, Profile.class); - i.putExtra(Profile.EXTRA_PROFILE, finalWhoApproved); - mContext.startActivity(i); - } else { - approveSubmission( - mContext, posts, submission, recyclerview, holder); - } - break; - case 2: - // todo this - break; - case 3: - if (isNsfw) { - unNsfwSubmission(mContext, submission, holder); - } else { - setPostNsfw(mContext, submission, holder); - } - break; - case 12: - if (isSpoiler) { - unSpoiler(mContext, submission, holder); - } else { - setSpoiler(mContext, submission, holder); - } - break; - case 9: - if (locked) { - unLockSubmission(mContext, submission, holder); - } else { - lockSubmission(mContext, submission, holder); - } - break; - case 4: - if (stickied) { - unStickySubmission(mContext, submission, holder); - } else { - stickySubmission(mContext, submission, holder); - } - break; - case 5: - if (distinguished) { - unDistinguishSubmission(mContext, submission, holder); - } else { - distinguishSubmission(mContext, submission, holder); - } - break; - case 6: - removeSubmission( - mContext, submission, posts, recyclerview, holder, false); - break; - case 7: - if (SettingValues.removalReasonType - == SettingValues.RemovalReasonType.TOOLBOX.ordinal() - && ToolboxUI.canShowRemoval( - submission.getSubredditName())) { - ToolboxUI.showRemoval( - mContext, - submission, - new ToolboxUI.CompletedRemovalCallback() { - @Override - public void onComplete(boolean success) { - if (success) { - SubmissionCache.removed.add( - submission.getFullName()); - SubmissionCache.approved.remove( - submission.getFullName()); - - SubmissionCache.updateInfoSpannable( - submission, - mContext, - submission.getSubredditName()); - - if (mContext instanceof ModQueue) { - final int pos = - posts.indexOf(submission); - posts.remove(submission); - - if (pos == 0) { - recyclerview - .getAdapter() - .notifyDataSetChanged(); - } else { - recyclerview - .getAdapter() - .notifyItemRemoved(pos + 1); - } - } else { - recyclerview - .getAdapter() - .notifyItemChanged( - holder - .getBindingAdapterPosition()); - } - Snackbar s = - Snackbar.make( - holder.itemView, - R.string.submission_removed, - Snackbar.LENGTH_LONG); - - LayoutUtils.showSnackbar(s); - - } else { - new AlertDialog.Builder(mContext) - .setTitle(R.string.err_general) - .setMessage( - R.string.err_retry_later) - .show(); - } - } - }); - } else { // Show a Slide reason dialog if we can't show a toolbox or - // reddit one - doRemoveSubmissionReason( - mContext, submission, posts, recyclerview, holder); - } - break; - case 30: - removeSubmission( - mContext, submission, posts, recyclerview, holder, true); - break; - case 8: - Intent i = new Intent(mContext, Profile.class); - i.putExtra(Profile.EXTRA_PROFILE, submission.getAuthor()); - mContext.startActivity(i); - break; - case 20: - doSetFlair(mContext, submission, holder); - break; - case 23: - // ban a user - showBan(mContext, holder.itemView, submission, "", "", "", ""); - break; - case 24: - ToolboxUI.showUsernotes( - mContext, - submission.getAuthor(), - submission.getSubredditName(), - "l," + submission.getId()); - break; - } - } - }); - - b.show(); - } - - private void doRemoveSubmissionReason( - final Activity mContext, - final Submission submission, - final List posts, - final RecyclerView recyclerview, - final SubmissionViewHolder holder) { - SubmissionBottomSheetActions.reason = ""; - new MaterialDialog.Builder(mContext) - .title(R.string.mod_remove_title) - .positiveText(R.string.btn_remove) - .alwaysCallInputCallback() - .input( - mContext.getString(R.string.mod_remove_hint), - mContext.getString(R.string.mod_remove_template), - false, - new MaterialDialog.InputCallback() { - @Override - public void onInput(MaterialDialog dialog, CharSequence input) { - SubmissionBottomSheetActions.reason = input.toString(); - } - }) - .inputType(InputType.TYPE_TEXT_FLAG_CAP_SENTENCES) - .neutralText(R.string.mod_remove_insert_draft) - .onPositive( - new MaterialDialog.SingleButtonCallback() { - @Override - public void onClick(final MaterialDialog dialog, DialogAction which) { - - removeSubmissionReason( - submission, mContext, posts, SubmissionBottomSheetActions.reason, holder, recyclerview); - } - }) - .negativeText(R.string.btn_cancel) - .onNegative(null) - .show(); - } - - private void removeSubmissionReason( - final Submission submission, - final Activity mContext, - final List posts, - final String reason, - final SubmissionViewHolder holder, - final RecyclerView recyclerview) { - new AsyncTask() { - - @Override - public void onPostExecute(Boolean b) { - if (b) { - SubmissionCache.removed.add(submission.getFullName()); - SubmissionCache.approved.remove(submission.getFullName()); - - SubmissionCache.updateInfoSpannable( - submission, mContext, submission.getSubredditName()); - - if (mContext instanceof ModQueue) { - final int pos = posts.indexOf(submission); - posts.remove(submission); - - if (pos == 0) { - recyclerview.getAdapter().notifyDataSetChanged(); - } else { - recyclerview.getAdapter().notifyItemRemoved(pos + 1); - } - } else { - recyclerview - .getAdapter() - .notifyItemChanged(holder.getBindingAdapterPosition()); - } - Snackbar s = - Snackbar.make( - holder.itemView, - R.string.submission_removed, - Snackbar.LENGTH_LONG); - - LayoutUtils.showSnackbar(s); - - } else { - new AlertDialog.Builder(mContext) - .setTitle(R.string.err_general) - .setMessage(R.string.err_retry_later) - .show(); - } - } - - @Override - protected Boolean doInBackground(Void... params) { - try { - String toDistinguish = - new AccountManager(Authentication.reddit).reply(submission, reason); - new ModerationManager(Authentication.reddit).remove(submission, false); - new ModerationManager(Authentication.reddit) - .setDistinguishedStatus( - Authentication.reddit.get("t1_" + toDistinguish).get(0), - DistinguishedStatus.MODERATOR); - } catch (ApiException e) { - e.printStackTrace(); - return false; - } - return true; - } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } - - private void removeSubmission( - final Activity mContext, - final Submission submission, - final List posts, - final RecyclerView recyclerview, - final SubmissionViewHolder holder, - final boolean spam) { - new AsyncTask() { - - @Override - public void onPostExecute(Boolean b) { - - SubmissionCache.removed.add(submission.getFullName()); - SubmissionCache.approved.remove(submission.getFullName()); - - SubmissionCache.updateInfoSpannable( - submission, mContext, submission.getSubredditName()); - - if (b) { - if (mContext instanceof ModQueue) { - final int pos = posts.indexOf(submission); - posts.remove(submission); - - if (pos == 0) { - recyclerview.getAdapter().notifyDataSetChanged(); - } else { - recyclerview.getAdapter().notifyItemRemoved(pos + 1); - } - } else { - recyclerview - .getAdapter() - .notifyItemChanged(holder.getBindingAdapterPosition()); - } - - Snackbar s = - Snackbar.make( - holder.itemView, - R.string.submission_removed, - Snackbar.LENGTH_LONG); - LayoutUtils.showSnackbar(s); - - } else { - new AlertDialog.Builder(mContext) - .setTitle(R.string.err_general) - .setMessage(R.string.err_retry_later) - .show(); - } - } - - @Override - protected Boolean doInBackground(Void... params) { - try { - new ModerationManager(Authentication.reddit).remove(submission, spam); - } catch (ApiException | NetworkException e) { - e.printStackTrace(); - return false; - } - return true; - } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } - - private void doSetFlair( - final Activity mContext, - final Submission submission, - final SubmissionViewHolder holder) { - new AsyncTask>() { - ArrayList flair; - - @Override - protected ArrayList doInBackground(Void... params) { - FlairReference allFlairs = - new FluentRedditClient(Authentication.reddit) - .subreddit(submission.getSubredditName()) - .flair(); - try { - flair = new ArrayList<>(allFlairs.options(submission)); - final ArrayList finalFlairs = new ArrayList<>(); - for (FlairTemplate temp : flair) { - finalFlairs.add(temp.getText()); - } - return finalFlairs; - } catch (Exception e) { - e.printStackTrace(); - // sub probably has no flairs? - } - return null; - } - - @Override - public void onPostExecute(final ArrayList data) { - try { - if (data.isEmpty()) { - new AlertDialog.Builder(mContext) - .setTitle(R.string.mod_flair_none_found) - .setPositiveButton(R.string.btn_ok, null) - .show(); - } else { - showFlairSelectionDialog(mContext, submission, data, flair, holder); - } - } catch (Exception ignored) { - - } - } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } - - private void showFlairSelectionDialog( - final Activity mContext, - final Submission submission, - ArrayList data, - final ArrayList flair, - final SubmissionViewHolder holder) { - new MaterialDialog.Builder(mContext) - .items(data) - .title(R.string.sidebar_select_flair) - .itemsCallback( - new MaterialDialog.ListCallback() { - @Override - public void onSelection( - MaterialDialog dialog, - View itemView, - int which, - CharSequence text) { - final FlairTemplate t = flair.get(which); - if (t.isTextEditable()) { - showFlairEditDialog(mContext, submission, t, holder); - } else { - setFlair(mContext, null, submission, t, holder); - } - } - }) - .show(); - } - - private void showFlairEditDialog( - final Activity mContext, - final Submission submission, - final FlairTemplate t, - final SubmissionViewHolder holder) { - new MaterialDialog.Builder(mContext) - .title(R.string.sidebar_select_flair_text) - .input( - mContext.getString(R.string.mod_flair_hint), - t.getText(), - true, - (dialog, input) -> {}) - .positiveText(R.string.btn_set) - .onPositive( - new MaterialDialog.SingleButtonCallback() { - @Override - public void onClick(MaterialDialog dialog, DialogAction which) { - final String flair = dialog.getInputEditText().getText().toString(); - setFlair(mContext, flair, submission, t, holder); - } - }) - .negativeText(R.string.btn_cancel) - .show(); - } - - private void setFlair( - final Context mContext, - final String flair, - final Submission submission, - final FlairTemplate t, - final SubmissionViewHolder holder) { - new AsyncTask() { - @Override - protected Boolean doInBackground(Void... params) { - try { - new ModerationManager(Authentication.reddit) - .setFlair(submission.getSubredditName(), t, flair, submission); - return true; - } catch (ApiException e) { - e.printStackTrace(); - return false; - } - } - - @Override - protected void onPostExecute(Boolean done) { - Snackbar s = null; - if (done) { - if (holder.itemView != null) { - s = - Snackbar.make( - holder.itemView, - R.string.snackbar_flair_success, - Snackbar.LENGTH_SHORT); - } - if (holder.itemView != null) { - SubmissionCache.updateTitleFlair(submission, flair, mContext); - doText(holder, submission, mContext, submission.getSubredditName(), false); - // Force the title view to re-measure itself - holder.title.requestLayout(); - } - } else { - if (holder.itemView != null) { - s = - Snackbar.make( - holder.itemView, - R.string.snackbar_flair_error, - Snackbar.LENGTH_SHORT); - } - } - if (s != null) { - LayoutUtils.showSnackbar(s); - } - } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } - - public void doText( - SubmissionViewHolder holder, - Submission submission, - Context mContext, - String baseSub, - boolean full) { - SpannableStringBuilder t = SubmissionCache.getTitleLine(submission, mContext); - SpannableStringBuilder l = SubmissionCache.getInfoLine(submission, mContext, baseSub); - SpannableStringBuilder c = SubmissionCache.getCrosspostLine(submission, mContext); - - int[] textSizeAttr = new int[] {R.attr.font_cardtitle, R.attr.font_cardinfo}; - TypedArray a = mContext.obtainStyledAttributes(textSizeAttr); - int textSizeT = a.getDimensionPixelSize(0, 18); - int textSizeI = a.getDimensionPixelSize(1, 14); - - t.setSpan(new AbsoluteSizeSpan(textSizeT), 0, t.length(), 0); - l.setSpan(new AbsoluteSizeSpan(textSizeI), 0, l.length(), 0); - - SpannableStringBuilder s = new SpannableStringBuilder(); - if (SettingValues.titleTop) { - s.append(t); - s.append("\n"); - s.append(l); - } else { - s.append(l); - s.append("\n"); - s.append(t); - } - if (!full && c != null) { - c.setSpan(new AbsoluteSizeSpan(textSizeI), 0, c.length(), 0); - s.append("\n"); - s.append(c); - } - a.recycle(); - - holder.title.setText(s); - - // Force this TextView to recalculate itself and request a new layout pass - holder.title.requestLayout(); - holder.title.invalidate(); - } - - private void stickySubmission( - final Activity mContext, - final Submission submission, - final SubmissionViewHolder holder) { - new AsyncTask() { - - @Override - public void onPostExecute(Boolean b) { - if (b) { - Snackbar s = - Snackbar.make( - holder.itemView, - R.string.really_pin_submission_message, - Snackbar.LENGTH_LONG); - LayoutUtils.showSnackbar(s); - - } else { - new AlertDialog.Builder(mContext) - .setTitle(R.string.err_general) - .setMessage(R.string.err_retry_later) - .show(); - } - } - - @Override - protected Boolean doInBackground(Void... params) { - try { - new ModerationManager(Authentication.reddit).setSticky(submission, true); - } catch (ApiException | NetworkException e) { - e.printStackTrace(); - return false; - } - return true; - } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } - - private void unStickySubmission( - final Activity mContext, - final Submission submission, - final SubmissionViewHolder holder) { - new AsyncTask() { - - @Override - public void onPostExecute(Boolean b) { - if (b) { - Snackbar s = - Snackbar.make( - holder.itemView, - R.string.really_unpin_submission_message, - Snackbar.LENGTH_LONG); - LayoutUtils.showSnackbar(s); - - } else { - new AlertDialog.Builder(mContext) - .setTitle(R.string.err_general) - .setMessage(R.string.err_retry_later) - .show(); - } - } - - @Override - protected Boolean doInBackground(Void... params) { - try { - new ModerationManager(Authentication.reddit).setSticky(submission, false); - } catch (ApiException e) { - e.printStackTrace(); - return false; - } - return true; - } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } - - private void lockSubmission( - final Activity mContext, - final Submission submission, - final SubmissionViewHolder holder) { - new AsyncTask() { - - @Override - public void onPostExecute(Boolean b) { - if (b) { - Snackbar s = - Snackbar.make( - holder.itemView, R.string.mod_locked, Snackbar.LENGTH_LONG); - LayoutUtils.showSnackbar(s); - - } else { - new AlertDialog.Builder(mContext) - .setTitle(R.string.err_general) - .setMessage(R.string.err_retry_later) - .show(); - } - } - - @Override - protected Boolean doInBackground(Void... params) { - try { - new ModerationManager(Authentication.reddit).setLocked(submission); - } catch (ApiException e) { - e.printStackTrace(); - return false; - } - return true; - } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } - - private void unLockSubmission( - final Activity mContext, - final Submission submission, - final SubmissionViewHolder holder) { - new AsyncTask() { - - @Override - public void onPostExecute(Boolean b) { - if (b) { - Snackbar s = - Snackbar.make( - holder.itemView, R.string.mod_unlocked, Snackbar.LENGTH_LONG); - LayoutUtils.showSnackbar(s); - - } else { - new AlertDialog.Builder(mContext) - .setTitle(R.string.err_general) - .setMessage(R.string.err_retry_later) - .show(); - } - } - - @Override - protected Boolean doInBackground(Void... params) { - try { - new ModerationManager(Authentication.reddit).setUnlocked(submission); - } catch (ApiException e) { - e.printStackTrace(); - return false; - } - return true; - } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } - - private void distinguishSubmission( - final Activity mContext, - final Submission submission, - final SubmissionViewHolder holder) { - new AsyncTask() { - - @Override - public void onPostExecute(Boolean b) { - if (b) { - Snackbar s = - Snackbar.make( - holder.itemView, - "Submission distinguished", - Snackbar.LENGTH_LONG); - LayoutUtils.showSnackbar(s); - - } else { - new AlertDialog.Builder(mContext) - .setTitle(R.string.err_general) - .setMessage(R.string.err_retry_later) - .show(); - } - } - - @Override - protected Boolean doInBackground(Void... params) { - try { - new ModerationManager(Authentication.reddit) - .setDistinguishedStatus(submission, DistinguishedStatus.MODERATOR); - } catch (ApiException e) { - e.printStackTrace(); - return false; - } - return true; - } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } - - private void unDistinguishSubmission( - final Activity mContext, - final Submission submission, - final SubmissionViewHolder holder) { - new AsyncTask() { - - @Override - public void onPostExecute(Boolean b) { - if (b) { - Snackbar s = - Snackbar.make( - holder.itemView, - "Submission distinguish removed", - Snackbar.LENGTH_LONG); - LayoutUtils.showSnackbar(s); - - } else { - new AlertDialog.Builder(mContext) - .setTitle(R.string.err_general) - .setMessage(R.string.err_retry_later) - .show(); - } - } - - @Override - protected Boolean doInBackground(Void... params) { - try { - new ModerationManager(Authentication.reddit) - .setDistinguishedStatus(submission, DistinguishedStatus.MODERATOR); - } catch (ApiException e) { - e.printStackTrace(); - return false; - } - return true; - } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } - - private void setPostNsfw( - final Activity mContext, - final Submission submission, - final SubmissionViewHolder holder) { - new AsyncTask() { - - @Override - public void onPostExecute(Boolean b) { - if (b) { - Snackbar s = - Snackbar.make(holder.itemView, "NSFW status set", Snackbar.LENGTH_LONG); - LayoutUtils.showSnackbar(s); - - } else { - new AlertDialog.Builder(mContext) - .setTitle(R.string.err_general) - .setMessage(R.string.err_retry_later) - .show(); - } - } - - @Override - protected Boolean doInBackground(Void... params) { - try { - new ModerationManager(Authentication.reddit).setNsfw(submission, true); - } catch (ApiException e) { - e.printStackTrace(); - return false; - } - return true; - } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } - - private void unNsfwSubmission( - final Context mContext, - final Submission submission, - final SubmissionViewHolder holder) { - // todo update view with NSFW tag - new AsyncTask() { - - @Override - public void onPostExecute(Boolean b) { - if (b) { - Snackbar s = - Snackbar.make( - holder.itemView, "NSFW status removed", Snackbar.LENGTH_LONG); - LayoutUtils.showSnackbar(s); - - } else { - new AlertDialog.Builder(mContext) - .setTitle(R.string.err_general) - .setMessage(R.string.err_retry_later) - .show(); - } - } - - @Override - protected Boolean doInBackground(Void... params) { - try { - new ModerationManager(Authentication.reddit).setNsfw(submission, false); - } catch (ApiException e) { - e.printStackTrace(); - return false; - } - return true; - } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } - - private void setSpoiler( - final Activity mContext, - final Submission submission, - final SubmissionViewHolder holder) { - new AsyncTask() { - - @Override - public void onPostExecute(Boolean b) { - if (b) { - Snackbar s = - Snackbar.make( - holder.itemView, "Spoiler status set", Snackbar.LENGTH_LONG); - LayoutUtils.showSnackbar(s); - - } else { - new AlertDialog.Builder(mContext) - .setTitle(R.string.err_general) - .setMessage(R.string.err_retry_later) - .show(); - } - } - - @Override - protected Boolean doInBackground(Void... params) { - try { - new ModerationManager(Authentication.reddit).setSpoiler(submission, true); - } catch (ApiException e) { - e.printStackTrace(); - return false; - } - return true; - } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } - - private void unSpoiler( - final Context mContext, - final Submission submission, - final SubmissionViewHolder holder) { - // todo update view with NSFW tag - new AsyncTask() { - - @Override - public void onPostExecute(Boolean b) { - if (b) { - Snackbar s = - Snackbar.make( - holder.itemView, - "Spoiler status removed", - Snackbar.LENGTH_LONG); - LayoutUtils.showSnackbar(s); - - } else { - new AlertDialog.Builder(mContext) - .setTitle(R.string.err_general) - .setMessage(R.string.err_retry_later) - .show(); - } - } - - @Override - protected Boolean doInBackground(Void... params) { - try { - new ModerationManager(Authentication.reddit).setSpoiler(submission, false); - } catch (ApiException e) { - e.printStackTrace(); - return false; - } - return true; - } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } - - private void approveSubmission( - final Context mContext, - final List posts, - final Submission submission, - final RecyclerView recyclerview, - final SubmissionViewHolder holder) { - new AsyncTask() { - - @Override - public void onPostExecute(Boolean b) { - if (b) { - SubmissionCache.approved.add(submission.getFullName()); - SubmissionCache.removed.remove(submission.getFullName()); - SubmissionCache.updateInfoSpannable( - submission, mContext, submission.getSubredditName()); - - if (mContext instanceof ModQueue) { - final int pos = posts.indexOf(submission); - posts.remove(submission); - - if (pos == 0) { - recyclerview.getAdapter().notifyDataSetChanged(); - } else { - recyclerview.getAdapter().notifyItemRemoved(pos + 1); - } - } else { - recyclerview - .getAdapter() - .notifyItemChanged(holder.getBindingAdapterPosition()); - } - - try { - Snackbar s = - Snackbar.make( - holder.itemView, - R.string.mod_approved, - Snackbar.LENGTH_LONG); - LayoutUtils.showSnackbar(s); - } catch (Exception ignored) { - - } - - } else { - new AlertDialog.Builder(mContext) - .setTitle(R.string.err_general) - .setMessage(R.string.err_retry_later) - .show(); - } - } - - @Override - protected Boolean doInBackground(Void... params) { - try { - new ModerationManager(Authentication.reddit).approve(submission); - } catch (ApiException e) { - e.printStackTrace(); - return false; - } - return true; - } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } - - public void showBan( - final Context mContext, - final View mToolbar, - final Submission submission, - String rs, - String nt, - String msg, - String t) { - LinearLayout l = new LinearLayout(mContext); - l.setOrientation(LinearLayout.VERTICAL); - int sixteen = DisplayUtil.dpToPxVertical(16); - l.setPadding(sixteen, 0, sixteen, 0); - - final EditText reason = new EditText(mContext); - reason.setHint(R.string.mod_ban_reason); - reason.setText(rs); - reason.setInputType(InputType.TYPE_TEXT_FLAG_CAP_SENTENCES); - l.addView(reason); - - final EditText note = new EditText(mContext); - note.setHint(R.string.mod_ban_note_mod); - note.setText(nt); - note.setInputType(InputType.TYPE_TEXT_FLAG_CAP_SENTENCES); - l.addView(note); - - final EditText message = new EditText(mContext); - message.setHint(R.string.mod_ban_note_user); - message.setText(msg); - message.setInputType(InputType.TYPE_TEXT_FLAG_CAP_SENTENCES); - l.addView(message); - - final EditText time = new EditText(mContext); - time.setHint(R.string.mod_ban_time); - time.setText(t); - time.setInputType(InputType.TYPE_CLASS_NUMBER); - l.addView(time); - - new AlertDialog.Builder(mContext) - .setView(l) - .setTitle(mContext.getString(R.string.mod_ban_title, submission.getAuthor())) - .setCancelable(true) - .setPositiveButton( - R.string.mod_btn_ban, - (dialog, which) -> { - // to ban - if (reason.getText().toString().isEmpty()) { - new AlertDialog.Builder(mContext) - .setTitle(R.string.mod_ban_reason_required) - .setMessage(R.string.misc_please_try_again) - .setPositiveButton( - R.string.btn_ok, - (dialog1, which1) -> - showBan( - mContext, - mToolbar, - submission, - reason.getText().toString(), - note.getText().toString(), - message.getText().toString(), - time.getText().toString())) - .setCancelable(false) - .show(); - } else { - new AsyncTask() { - @Override - protected Boolean doInBackground(Void... params) { - try { - String n = note.getText().toString(); - String m = message.getText().toString(); - - if (n.isEmpty()) { - n = null; - } - if (m.isEmpty()) { - m = null; - } - if (time.getText().toString().isEmpty()) { - new ModerationManager(Authentication.reddit) - .banUserPermanently( - submission.getSubredditName(), - submission.getAuthor(), - reason.getText().toString(), - n, - m); - } else { - new ModerationManager(Authentication.reddit) - .banUser( - submission.getSubredditName(), - submission.getAuthor(), - reason.getText().toString(), - n, - m, - Integer.parseInt( - time.getText().toString())); - } - return true; - } catch (Exception e) { - if (e instanceof InvalidScopeException) { - scope = true; - } - e.printStackTrace(); - return false; - } - } - - boolean scope; - - @Override - protected void onPostExecute(Boolean done) { - Snackbar s; - if (done) { - s = - Snackbar.make( - mToolbar, - R.string.mod_ban_success, - Snackbar.LENGTH_SHORT); - } else { - if (scope) { - new AlertDialog.Builder(mContext) - .setTitle(R.string.mod_ban_reauth) - .setMessage( - R.string.mod_ban_reauth_question) - .setPositiveButton( - R.string.btn_ok, - (dialog12, which12) -> { - Intent i = - new Intent( - mContext, - Reauthenticate - .class); - mContext.startActivity(i); - }) - .setNegativeButton( - R.string.misc_maybe_later, null) - .setCancelable(false) - .show(); - } - s = - Snackbar.make( - mToolbar, - R.string.mod_ban_fail, - Snackbar.LENGTH_INDEFINITE) - .setAction( - R.string.misc_try_again, - new View.OnClickListener() { - @Override - public void onClick( - View v) { - showBan( - mContext, - mToolbar, - submission, - reason.getText() - .toString(), - note.getText() - .toString(), - message.getText() - .toString(), - time.getText() - .toString()); - } - }); - } - - if (s != null) { - LayoutUtils.showSnackbar(s); - } - } - }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - } - }) - .setNegativeButton(R.string.btn_cancel, null) - .show(); - } public void populateSubmissionViewHolder( final SubmissionViewHolder holder, @@ -1423,7 +124,7 @@ public void populateSubmissionViewHolder( new View.OnClickListener() { @Override public void onClick(View v) { - showModBottomSheet( + SubmissionModActions.showModBottomSheet( mContext, submission, posts, @@ -1723,7 +424,7 @@ public boolean onLongClick(View v) { } }); - doText(holder, submission, mContext, baseSub, full); + SubmissionModActions.doText(holder, submission, mContext, baseSub, full); if (!full && SettingValues.isSelftextEnabled(baseSub) @@ -2627,12 +1328,12 @@ public void onClick( break; case 4: if (submission.isNsfw()) { - unNsfwSubmission( + SubmissionModActions.unNsfwSubmission( mContext, submission, holder); } else { - setPostNsfw( + SubmissionModActions.setPostNsfw( mContext, submission, holder); @@ -2643,12 +1344,12 @@ public void onClick( .getDataNode() .get("spoiler") .asBoolean()) { - unSpoiler( + SubmissionModActions.unSpoiler( mContext, submission, holder); } else { - setSpoiler( + SubmissionModActions.setSpoiler( mContext, submission, holder); diff --git a/app/src/main/java/me/edgan/redditslide/util/SubmissionModActions.java b/app/src/main/java/me/edgan/redditslide/util/SubmissionModActions.java new file mode 100644 index 000000000..c6c6ee182 --- /dev/null +++ b/app/src/main/java/me/edgan/redditslide/util/SubmissionModActions.java @@ -0,0 +1,1166 @@ +package me.edgan.redditslide.util; + +import android.app.Activity; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.graphics.Color; +import android.graphics.drawable.Drawable; +import android.os.AsyncTask; +import android.text.InputType; +import android.text.SpannableStringBuilder; +import android.text.style.AbsoluteSizeSpan; +import android.view.View; +import android.widget.EditText; +import android.widget.LinearLayout; + +import androidx.appcompat.app.AlertDialog; +import androidx.core.content.res.ResourcesCompat; +import androidx.recyclerview.widget.RecyclerView; + +import com.afollestad.materialdialogs.DialogAction; +import com.afollestad.materialdialogs.MaterialDialog; +import com.cocosw.bottomsheet.BottomSheet; +import com.google.android.material.snackbar.Snackbar; + +import net.dean.jraw.ApiException; +import net.dean.jraw.fluent.FlairReference; +import net.dean.jraw.fluent.FluentRedditClient; +import net.dean.jraw.http.NetworkException; +import net.dean.jraw.http.oauth.InvalidScopeException; +import net.dean.jraw.managers.AccountManager; +import net.dean.jraw.managers.ModerationManager; +import net.dean.jraw.models.Contribution; +import net.dean.jraw.models.DistinguishedStatus; +import net.dean.jraw.models.FlairTemplate; +import net.dean.jraw.models.Submission; +import net.dean.jraw.models.Thing; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import me.edgan.redditslide.Activities.ModQueue; +import me.edgan.redditslide.Activities.Profile; +import me.edgan.redditslide.Activities.Reauthenticate; +import me.edgan.redditslide.Adapters.SubmissionViewHolder; +import me.edgan.redditslide.Authentication; +import me.edgan.redditslide.R; +import me.edgan.redditslide.SettingValues; +import me.edgan.redditslide.SubmissionCache; +import me.edgan.redditslide.Toolbox.ToolboxUI; + +/** + * Handles Moderation actions for Submission views. + */ +public class SubmissionModActions { + + // Keep reason variable here as it's used within the mod action flow + public static String reason; + + public static void showModBottomSheet( + final Activity mContext, + final Submission submission, + final List posts, + final SubmissionViewHolder holder, + final RecyclerView recyclerview, + final Map reports, + final Map reports2) { + + final Resources res = mContext.getResources(); + int[] attrs = new int[] {R.attr.tintColor}; + TypedArray ta = mContext.obtainStyledAttributes(attrs); + + int color = ta.getColor(0, Color.WHITE); + Drawable profile = ResourcesCompat.getDrawable(mContext.getResources(), R.drawable.ic_account_circle, null); + final Drawable report = ResourcesCompat.getDrawable(mContext.getResources(), R.drawable.ic_report, null); + final Drawable approve = ResourcesCompat.getDrawable(mContext.getResources(), R.drawable.ic_thumb_up, null); + final Drawable nsfw = ResourcesCompat.getDrawable(mContext.getResources(), R.drawable.ic_visibility_off, null); + final Drawable spoiler = ResourcesCompat.getDrawable(mContext.getResources(), R.drawable.ic_remove_circle, null); + final Drawable pin = ResourcesCompat.getDrawable(mContext.getResources(), R.drawable.ic_bookmark_border, null); + final Drawable lock = ResourcesCompat.getDrawable(mContext.getResources(), R.drawable.ic_lock, null); + final Drawable flair = ResourcesCompat.getDrawable(mContext.getResources(), R.drawable.ic_format_quote, null); + final Drawable remove = ResourcesCompat.getDrawable(mContext.getResources(), R.drawable.ic_close, null); + final Drawable remove_reason = ResourcesCompat.getDrawable(mContext.getResources(), R.drawable.ic_announcement, null); + final Drawable ban = ResourcesCompat.getDrawable(mContext.getResources(), R.drawable.ic_gavel, null); + final Drawable spam = ResourcesCompat.getDrawable(mContext.getResources(), R.drawable.ic_flag, null); + final Drawable distinguish = ResourcesCompat.getDrawable(mContext.getResources(), R.drawable.ic_star, null); + final Drawable note = ResourcesCompat.getDrawable(mContext.getResources(), R.drawable.ic_note, null); + + final List drawableSet = Arrays.asList(profile, report, approve, spam, nsfw, pin, flair, remove, spoiler, remove_reason, ban, spam, distinguish, lock, note); + BlendModeUtil.tintDrawablesAsSrcAtop(drawableSet, color); + + ta.recycle(); + + BottomSheet.Builder b = new BottomSheet.Builder(mContext).title(CompatUtil.fromHtml(submission.getTitle())); + + int reportCount = reports.size() + reports2.size(); + + b.sheet(0, report, res.getQuantityString(R.plurals.mod_btn_reports, reportCount, reportCount)); + + if (SettingValues.toolboxEnabled) { + b.sheet(24, note, res.getString(R.string.mod_usernotes_view)); + } + + boolean approved = false; + String whoApproved = ""; + b.sheet(1, approve, res.getString(R.string.mod_btn_approve)); + b.sheet(6, remove, mContext.getString(R.string.mod_btn_remove)) + .sheet(7, remove_reason, res.getString(R.string.mod_btn_remove_reason)).sheet(30, spam, res.getString(R.string.mod_btn_spam)); + + b.sheet(20, flair, res.getString(R.string.mod_btn_submission_flair)); + + final boolean isNsfw = submission.isNsfw(); + if (isNsfw) { + b.sheet(3, nsfw, res.getString(R.string.mod_btn_unmark_nsfw)); + } else { + b.sheet(3, nsfw, res.getString(R.string.mod_btn_mark_nsfw)); + } + + final boolean isSpoiler = submission.getDataNode().get("spoiler").asBoolean(); + if (isSpoiler) { + b.sheet(12, nsfw, res.getString(R.string.mod_btn_unmark_spoiler)); + } else { + b.sheet(12, nsfw, res.getString(R.string.mod_btn_mark_spoiler)); + } + + final boolean locked = submission.isLocked(); + if (locked) { + b.sheet(9, lock, res.getString(R.string.mod_btn_unlock_thread)); + } else { + b.sheet(9, lock, res.getString(R.string.mod_btn_lock_thread)); + } + + final boolean stickied = submission.isStickied(); + if (!SubmissionCache.removed.contains(submission.getFullName())) { + if (stickied) { + b.sheet(4, pin, res.getString(R.string.mod_btn_unpin)); + } else { + b.sheet(4, pin, res.getString(R.string.mod_btn_pin)); + } + } + + final boolean distinguished = submission.getDistinguishedStatus() == DistinguishedStatus.MODERATOR || submission.getDistinguishedStatus() == DistinguishedStatus.ADMIN; + if (submission.getAuthor().equalsIgnoreCase(Authentication.name)) { + if (distinguished) { + b.sheet(5, distinguish, res.getString(R.string.mod_btn_undistinguish)); + } else { + b.sheet(5, distinguish, res.getString(R.string.mod_btn_distinguish)); + } + } + + final String finalWhoApproved = whoApproved; + final boolean finalApproved = approved; + b.sheet(8, profile, res.getString(R.string.mod_btn_author)); + b.sheet(23, ban, mContext.getString(R.string.mod_ban_user)); + b.listener( + new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int which) { + switch (which) { + case 0: + new AsyncTask>() { + @Override + protected ArrayList doInBackground(Void... params) { + + ArrayList finalReports = new ArrayList<>(); + for (Map.Entry entry : reports.entrySet()) { + finalReports.add(entry.getValue() + "× " + entry.getKey()); + } + for (Map.Entry entry : reports2.entrySet()) { + finalReports.add(entry.getKey() + ": " + entry.getValue()); + } + if (finalReports.isEmpty()) { + finalReports.add(mContext.getString(R.string.mod_no_reports)); + } + return finalReports; + } + + @Override + public void onPostExecute(ArrayList data) { + new AlertDialog.Builder(mContext) + .setTitle(R.string.mod_reports) + .setItems(data.toArray(new CharSequence[0]), null) + .show(); + } + }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + + break; + case 1: + if (finalApproved) { + Intent i = new Intent(mContext, Profile.class); + i.putExtra(Profile.EXTRA_PROFILE, finalWhoApproved); + mContext.startActivity(i); + } else { + approveSubmission( + mContext, posts, submission, recyclerview, holder); + } + break; + case 2: + // todo this + break; + case 3: + if (isNsfw) { + unNsfwSubmission(mContext, submission, holder); + } else { + setPostNsfw(mContext, submission, holder); + } + break; + case 12: + if (isSpoiler) { + unSpoiler(mContext, submission, holder); + } else { + setSpoiler(mContext, submission, holder); + } + break; + case 9: + if (locked) { + unLockSubmission(mContext, submission, holder); + } else { + lockSubmission(mContext, submission, holder); + } + break; + case 4: + if (stickied) { + unStickySubmission(mContext, submission, holder); + } else { + stickySubmission(mContext, submission, holder); + } + break; + case 5: + if (distinguished) { + unDistinguishSubmission(mContext, submission, holder); + } else { + distinguishSubmission(mContext, submission, holder); + } + break; + case 6: + removeSubmission( + mContext, submission, posts, recyclerview, holder, false); + break; + case 7: + if (SettingValues.removalReasonType + == SettingValues.RemovalReasonType.TOOLBOX.ordinal() + && ToolboxUI.canShowRemoval( + submission.getSubredditName())) { + ToolboxUI.showRemoval( + mContext, + submission, + new ToolboxUI.CompletedRemovalCallback() { + @Override + public void onComplete(boolean success) { + if (success) { + SubmissionCache.removed.add(submission.getFullName()); + SubmissionCache.approved.remove(submission.getFullName()); + + SubmissionCache.updateInfoSpannable(submission, mContext, submission.getSubredditName()); + + if (mContext instanceof ModQueue) { + final int pos = posts.indexOf(submission); + posts.remove(submission); + + if (pos == 0) { + recyclerview.getAdapter().notifyDataSetChanged(); + } else { + recyclerview.getAdapter().notifyItemRemoved(pos + 1); + } + } else { + recyclerview.getAdapter().notifyItemChanged(holder.getBindingAdapterPosition()); + } + + Snackbar s = Snackbar.make(holder.itemView, R.string.submission_removed, Snackbar.LENGTH_LONG); + LayoutUtils.showSnackbar(s); + } else { + new AlertDialog.Builder(mContext) + .setTitle(R.string.err_general) + .setMessage(R.string.err_retry_later) + .show(); + } + } + }); + } else { // Show a Slide reason dialog if we can't show a toolbox or rddit one + doRemoveSubmissionReason(mContext, submission, posts, recyclerview, holder); + } + + break; + case 30: + removeSubmission(mContext, submission, posts, recyclerview, holder, true); + + break; + case 8: + Intent i = new Intent(mContext, Profile.class); + i.putExtra(Profile.EXTRA_PROFILE, submission.getAuthor()); + mContext.startActivity(i); + + break; + case 20: + doSetFlair(mContext, submission, holder); + + break; + case 23: + // ban a user + showBan(mContext, holder.itemView, submission, "", "", "", ""); + + break; + case 24: + ToolboxUI.showUsernotes(mContext, submission.getAuthor(), submission.getSubredditName(), "l," + submission.getId()); + + break; + } + } + }); + + b.show(); + } + + public static void doRemoveSubmissionReason( + final Activity mContext, + final Submission submission, + final List posts, + final RecyclerView recyclerview, + final SubmissionViewHolder holder) { + reason = ""; + new MaterialDialog.Builder(mContext) + .title(R.string.mod_remove_title) + .positiveText(R.string.btn_remove) + .alwaysCallInputCallback() + .input( + mContext.getString(R.string.mod_remove_hint), + mContext.getString(R.string.mod_remove_template), + false, + new MaterialDialog.InputCallback() { + @Override + public void onInput(MaterialDialog dialog, CharSequence input) { + reason = input.toString(); + } + }) + .inputType(InputType.TYPE_TEXT_FLAG_CAP_SENTENCES) + .neutralText(R.string.mod_remove_insert_draft) + .onPositive( + new MaterialDialog.SingleButtonCallback() { + @Override + public void onClick(final MaterialDialog dialog, DialogAction which) { + + removeSubmissionReason( + submission, mContext, posts, reason, holder, recyclerview); + } + }) + .negativeText(R.string.btn_cancel) + .onNegative(null) + .show(); + } + + public static void removeSubmissionReason( + final Submission submission, + final Activity mContext, + final List posts, + final String reason, + final SubmissionViewHolder holder, + final RecyclerView recyclerview) { + new AsyncTask() { + + @Override + public void onPostExecute(Boolean b) { + if (b) { + SubmissionCache.removed.add(submission.getFullName()); + SubmissionCache.approved.remove(submission.getFullName()); + + SubmissionCache.updateInfoSpannable( + submission, mContext, submission.getSubredditName()); + + if (mContext instanceof ModQueue) { + final int pos = posts.indexOf(submission); + posts.remove(submission); + + if (pos == 0) { + recyclerview.getAdapter().notifyDataSetChanged(); + } else { + recyclerview.getAdapter().notifyItemRemoved(pos + 1); + } + } else { + recyclerview + .getAdapter() + .notifyItemChanged(holder.getBindingAdapterPosition()); + } + Snackbar s = + Snackbar.make( + holder.itemView, + R.string.submission_removed, + Snackbar.LENGTH_LONG); + + LayoutUtils.showSnackbar(s); + + } else { + new AlertDialog.Builder(mContext) + .setTitle(R.string.err_general) + .setMessage(R.string.err_retry_later) + .show(); + } + } + + @Override + protected Boolean doInBackground(Void... params) { + try { + String toDistinguish = + new AccountManager(Authentication.reddit).reply(submission, reason); + new ModerationManager(Authentication.reddit).remove(submission, false); + new ModerationManager(Authentication.reddit) + .setDistinguishedStatus( + Authentication.reddit.get("t1_" + toDistinguish).get(0), + DistinguishedStatus.MODERATOR); + } catch (ApiException e) { + e.printStackTrace(); + return false; + } + return true; + } + }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + public static void removeSubmission( + final Activity mContext, + final Submission submission, + final List posts, + final RecyclerView recyclerview, + final SubmissionViewHolder holder, + final boolean spam) { + new AsyncTask() { + + @Override + public void onPostExecute(Boolean b) { + + SubmissionCache.removed.add(submission.getFullName()); + SubmissionCache.approved.remove(submission.getFullName()); + + SubmissionCache.updateInfoSpannable( + submission, mContext, submission.getSubredditName()); + + if (b) { + if (mContext instanceof ModQueue) { + final int pos = posts.indexOf(submission); + posts.remove(submission); + + if (pos == 0) { + recyclerview.getAdapter().notifyDataSetChanged(); + } else { + recyclerview.getAdapter().notifyItemRemoved(pos + 1); + } + } else { + recyclerview + .getAdapter() + .notifyItemChanged(holder.getBindingAdapterPosition()); + } + + Snackbar s = + Snackbar.make( + holder.itemView, + R.string.submission_removed, + Snackbar.LENGTH_LONG); + LayoutUtils.showSnackbar(s); + + } else { + new AlertDialog.Builder(mContext) + .setTitle(R.string.err_general) + .setMessage(R.string.err_retry_later) + .show(); + } + } + + @Override + protected Boolean doInBackground(Void... params) { + try { + new ModerationManager(Authentication.reddit).remove(submission, spam); + } catch (ApiException | NetworkException e) { + e.printStackTrace(); + return false; + } + return true; + } + }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + public static void doSetFlair( + final Activity mContext, + final Submission submission, + final SubmissionViewHolder holder) { + new AsyncTask>() { + ArrayList flair; + + @Override + protected ArrayList doInBackground(Void... params) { + FlairReference allFlairs = + new FluentRedditClient(Authentication.reddit) + .subreddit(submission.getSubredditName()) + .flair(); + try { + flair = new ArrayList<>(allFlairs.options(submission)); + final ArrayList finalFlairs = new ArrayList<>(); + for (FlairTemplate temp : flair) { + finalFlairs.add(temp.getText()); + } + return finalFlairs; + } catch (Exception e) { + e.printStackTrace(); + // sub probably has no flairs? + } + return null; + } + + @Override + public void onPostExecute(final ArrayList data) { + try { + if (data == null || data.isEmpty()) { // Added null check + new AlertDialog.Builder(mContext) + .setTitle(R.string.mod_flair_none_found) + .setPositiveButton(R.string.btn_ok, null) + .show(); + } else { + showFlairSelectionDialog(mContext, submission, data, flair, holder); + } + } catch (Exception ignored) { + + } + } + }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + public static void showFlairSelectionDialog( + final Activity mContext, + final Submission submission, + ArrayList data, + final ArrayList flair, + final SubmissionViewHolder holder) { + new MaterialDialog.Builder(mContext) + .items(data) + .title(R.string.sidebar_select_flair) + .itemsCallback( + new MaterialDialog.ListCallback() { + @Override + public void onSelection( + MaterialDialog dialog, + View itemView, + int which, + CharSequence text) { + final FlairTemplate t = flair.get(which); + if (t.isTextEditable()) { + showFlairEditDialog(mContext, submission, t, holder); + } else { + setFlair(mContext, null, submission, t, holder); + } + } + }) + .show(); + } + + public static void showFlairEditDialog( + final Activity mContext, + final Submission submission, + final FlairTemplate t, + final SubmissionViewHolder holder) { + new MaterialDialog.Builder(mContext) + .title(R.string.sidebar_select_flair_text) + .input( + mContext.getString(R.string.mod_flair_hint), + t.getText(), + true, + (dialog, input) -> {}) + .positiveText(R.string.btn_set) + .onPositive( + new MaterialDialog.SingleButtonCallback() { + @Override + public void onClick(MaterialDialog dialog, DialogAction which) { + final String flair = dialog.getInputEditText().getText().toString(); + setFlair(mContext, flair, submission, t, holder); + } + }) + .negativeText(R.string.btn_cancel) + .show(); + } + + public static void setFlair( + final Context mContext, + final String flair, + final Submission submission, + final FlairTemplate t, + final SubmissionViewHolder holder) { + new AsyncTask() { + @Override + protected Boolean doInBackground(Void... params) { + try { + new ModerationManager(Authentication.reddit) + .setFlair(submission.getSubredditName(), t, flair, submission); + return true; + } catch (ApiException e) { + e.printStackTrace(); + return false; + } + } + + @Override + protected void onPostExecute(Boolean done) { + Snackbar s = null; + if (done) { + if (holder.itemView != null) { + s = Snackbar.make(holder.itemView, R.string.snackbar_flair_success, Snackbar.LENGTH_SHORT); + } + if (holder.itemView != null) { + SubmissionCache.updateTitleFlair(submission, flair != null ? flair : t.getText(), mContext); // Use flair if not null, else template text + doText(holder, submission, mContext, submission.getSubredditName(), false); + // Force the title view to re-measure itself + holder.title.requestLayout(); + } + } else { + if (holder.itemView != null) { + s = Snackbar.make(holder.itemView, R.string.snackbar_flair_error, Snackbar.LENGTH_SHORT); + } + } + if (s != null) { + LayoutUtils.showSnackbar(s); + } + } + }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + public static void doText(SubmissionViewHolder holder, Submission submission, Context mContext, String baseSub, boolean full) { + SpannableStringBuilder t = SubmissionCache.getTitleLine(submission, mContext); + SpannableStringBuilder l = SubmissionCache.getInfoLine(submission, mContext, baseSub); + SpannableStringBuilder c = SubmissionCache.getCrosspostLine(submission, mContext); + + int[] textSizeAttr = new int[] {R.attr.font_cardtitle, R.attr.font_cardinfo}; + TypedArray a = mContext.obtainStyledAttributes(textSizeAttr); + int textSizeT = a.getDimensionPixelSize(0, 18); + int textSizeI = a.getDimensionPixelSize(1, 14); + + t.setSpan(new AbsoluteSizeSpan(textSizeT), 0, t.length(), 0); + l.setSpan(new AbsoluteSizeSpan(textSizeI), 0, l.length(), 0); + + SpannableStringBuilder s = new SpannableStringBuilder(); + if (SettingValues.titleTop) { + s.append(t); + s.append("\n"); + s.append(l); + } else { + s.append(l); + s.append("\n"); + s.append(t); + } + if (!full && c != null) { + c.setSpan(new AbsoluteSizeSpan(textSizeI), 0, c.length(), 0); + s.append("\n"); + s.append(c); + } + a.recycle(); + + holder.title.setText(s); + + // Force this TextView to recalculate itself and request a new layout pass + holder.title.requestLayout(); + holder.title.invalidate(); + } + + public static void stickySubmission( + final Activity mContext, + final Submission submission, + final SubmissionViewHolder holder) { + new AsyncTask() { + + @Override + public void onPostExecute(Boolean b) { + if (b) { + Snackbar s = Snackbar.make(holder.itemView, R.string.really_pin_submission_message, Snackbar.LENGTH_LONG); + LayoutUtils.showSnackbar(s); + + } else { + new AlertDialog.Builder(mContext) + .setTitle(R.string.err_general) + .setMessage(R.string.err_retry_later) + .show(); + } + } + + @Override + protected Boolean doInBackground(Void... params) { + try { + new ModerationManager(Authentication.reddit).setSticky(submission, true); + } catch (ApiException | NetworkException e) { + e.printStackTrace(); + + return false; + } + return true; + } + }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + public static void unStickySubmission( + final Activity mContext, + final Submission submission, + final SubmissionViewHolder holder) { + new AsyncTask() { + + @Override + public void onPostExecute(Boolean b) { + if (b) { + Snackbar s = Snackbar.make(holder.itemView, R.string.really_unpin_submission_message, Snackbar.LENGTH_LONG); + LayoutUtils.showSnackbar(s); + + } else { + new AlertDialog.Builder(mContext) + .setTitle(R.string.err_general) + .setMessage(R.string.err_retry_later) + .show(); + } + } + + @Override + protected Boolean doInBackground(Void... params) { + try { + new ModerationManager(Authentication.reddit).setSticky(submission, false); + } catch (ApiException e) { + e.printStackTrace(); + return false; + } + return true; + } + }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + public static void lockSubmission( + final Activity mContext, + final Submission submission, + final SubmissionViewHolder holder) { + new AsyncTask() { + + @Override + public void onPostExecute(Boolean b) { + if (b) { + Snackbar s = Snackbar.make(holder.itemView, R.string.mod_locked, Snackbar.LENGTH_LONG); + LayoutUtils.showSnackbar(s); + + } else { + new AlertDialog.Builder(mContext) + .setTitle(R.string.err_general) + .setMessage(R.string.err_retry_later) + .show(); + } + } + + @Override + protected Boolean doInBackground(Void... params) { + try { + new ModerationManager(Authentication.reddit).setLocked(submission); + } catch (ApiException e) { + e.printStackTrace(); + return false; + } + return true; + } + }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + public static void unLockSubmission(final Activity mContext, final Submission submission, final SubmissionViewHolder holder) { + new AsyncTask() { + @Override + public void onPostExecute(Boolean b) { + if (b) { + Snackbar s = Snackbar.make(holder.itemView, R.string.mod_unlocked, Snackbar.LENGTH_LONG); + LayoutUtils.showSnackbar(s); + + } else { + new AlertDialog.Builder(mContext) + .setTitle(R.string.err_general) + .setMessage(R.string.err_retry_later) + .show(); + } + } + + @Override + protected Boolean doInBackground(Void... params) { + try { + new ModerationManager(Authentication.reddit).setUnlocked(submission); + } catch (ApiException e) { + e.printStackTrace(); + return false; + } + return true; + } + }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + public static void distinguishSubmission(final Activity mContext, final Submission submission, final SubmissionViewHolder holder) { + new AsyncTask() { + + @Override + public void onPostExecute(Boolean b) { + if (b) { + Snackbar s = Snackbar.make(holder.itemView, R.string.submission_distinguished, Snackbar.LENGTH_LONG); + LayoutUtils.showSnackbar(s); + + } else { + new AlertDialog.Builder(mContext) + .setTitle(R.string.err_general) + .setMessage(R.string.err_retry_later) + .show(); + } + } + + @Override + protected Boolean doInBackground(Void... params) { + try { + new ModerationManager(Authentication.reddit).setDistinguishedStatus(submission, DistinguishedStatus.MODERATOR); + } catch (ApiException e) { + e.printStackTrace(); + + return false; + } + return true; + } + }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + public static void unDistinguishSubmission(final Activity mContext, final Submission submission, final SubmissionViewHolder holder) { + new AsyncTask() { + @Override + public void onPostExecute(Boolean b) { + if (b) { + Snackbar s = Snackbar.make(holder.itemView, R.string.submission_distinguished_removed, Snackbar.LENGTH_LONG); + LayoutUtils.showSnackbar(s); + } else { + new AlertDialog.Builder(mContext) + .setTitle(R.string.err_general) + .setMessage(R.string.err_retry_later) + .show(); + } + } + + @Override + protected Boolean doInBackground(Void... params) { + try { + // JRAW requires MODERATOR to undistinguish as well + new ModerationManager(Authentication.reddit).setDistinguishedStatus(submission, DistinguishedStatus.MODERATOR); + } catch (ApiException e) { + e.printStackTrace(); + + return false; + } + return true; + } + }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + public static void setPostNsfw(final Activity mContext, final Submission submission, final SubmissionViewHolder holder) { + new AsyncTask() { + @Override + public void onPostExecute(Boolean b) { + if (b) { + Snackbar s = Snackbar.make(holder.itemView, R.string.nsfw_status_set, Snackbar.LENGTH_LONG); + LayoutUtils.showSnackbar(s); + // TODO: Update UI immediately + + } else { + new AlertDialog.Builder(mContext) + .setTitle(R.string.err_general) + .setMessage(R.string.err_retry_later) + .show(); + } + } + + @Override + protected Boolean doInBackground(Void... params) { + try { + new ModerationManager(Authentication.reddit).setNsfw(submission, true); + } catch (ApiException e) { + e.printStackTrace(); + + return false; + } + return true; + } + }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + public static void unNsfwSubmission(final Context mContext, final Submission submission, final SubmissionViewHolder holder) { + // todo update view with NSFW tag + new AsyncTask() { + @Override + public void onPostExecute(Boolean b) { + if (b) { + Snackbar s = Snackbar.make(holder.itemView, R.string.nsfw_status_removed, Snackbar.LENGTH_LONG); + LayoutUtils.showSnackbar(s); + // TODO: Update UI immediately + + } else { + new AlertDialog.Builder(mContext) + .setTitle(R.string.err_general) + .setMessage(R.string.err_retry_later) + .show(); + } + } + + @Override + protected Boolean doInBackground(Void... params) { + try { + new ModerationManager(Authentication.reddit).setNsfw(submission, false); + } catch (ApiException e) { + e.printStackTrace(); + + return false; + } + return true; + } + }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + public static void setSpoiler( + final Activity mContext, + final Submission submission, + final SubmissionViewHolder holder) { + new AsyncTask() { + + @Override + public void onPostExecute(Boolean b) { + if (b) { + Snackbar s = Snackbar.make(holder.itemView, R.string.spoiler_status_set, Snackbar.LENGTH_LONG); + LayoutUtils.showSnackbar(s); + // TODO: Update UI immediately + + } else { + new AlertDialog.Builder(mContext) + .setTitle(R.string.err_general) + .setMessage(R.string.err_retry_later) + .show(); + } + } + + @Override + protected Boolean doInBackground(Void... params) { + try { + new ModerationManager(Authentication.reddit).setSpoiler(submission, true); + } catch (ApiException e) { + e.printStackTrace(); + + return false; + } + return true; + } + }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + public static void unSpoiler(final Context mContext, final Submission submission, final SubmissionViewHolder holder) { + // todo update view with NSFW tag + new AsyncTask() { + + @Override + public void onPostExecute(Boolean b) { + if (b) { + Snackbar s = Snackbar.make(holder.itemView, R.string.spoiler_status_removed, Snackbar.LENGTH_LONG); + LayoutUtils.showSnackbar(s); + // TODO: Update UI immediately + + } else { + new AlertDialog.Builder(mContext) + .setTitle(R.string.err_general) + .setMessage(R.string.err_retry_later) + .show(); + } + } + + @Override + protected Boolean doInBackground(Void... params) { + try { + new ModerationManager(Authentication.reddit).setSpoiler(submission, false); + } catch (ApiException e) { + e.printStackTrace(); + + return false; + } + return true; + } + }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + public static void approveSubmission(final Context mContext, final List posts, final Submission submission, final RecyclerView recyclerview, final SubmissionViewHolder holder) { + new AsyncTask() { + @Override + public void onPostExecute(Boolean b) { + if (b) { + SubmissionCache.approved.add(submission.getFullName()); + SubmissionCache.removed.remove(submission.getFullName()); + SubmissionCache.updateInfoSpannable(submission, mContext, submission.getSubredditName()); + + if (mContext instanceof ModQueue) { + final int pos = posts.indexOf(submission); + posts.remove(submission); + + if (pos == 0) { + recyclerview.getAdapter().notifyDataSetChanged(); + } else { + recyclerview.getAdapter().notifyItemRemoved(pos + 1); + } + } else { + recyclerview.getAdapter().notifyItemChanged(holder.getBindingAdapterPosition()); + } + + try { + Snackbar s = Snackbar.make(holder.itemView, R.string.mod_approved, Snackbar.LENGTH_LONG); + LayoutUtils.showSnackbar(s); + } catch (Exception ignored) {} + } else { + new AlertDialog.Builder(mContext) + .setTitle(R.string.err_general) + .setMessage(R.string.err_retry_later) + .show(); + } + } + + @Override + protected Boolean doInBackground(Void... params) { + try { + new ModerationManager(Authentication.reddit).approve(submission); + } catch (ApiException e) { + e.printStackTrace(); + + return false; + } + return true; + } + }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + + public static void showBan(final Context mContext, final View mToolbar, final Submission submission, String rs, String nt, String msg, String t) { + LinearLayout l = new LinearLayout(mContext); + l.setOrientation(LinearLayout.VERTICAL); + int sixteen = DisplayUtil.dpToPxVertical(16); + l.setPadding(sixteen, 0, sixteen, 0); + + final EditText reason = new EditText(mContext); + reason.setHint(R.string.mod_ban_reason); + reason.setText(rs); + reason.setInputType(InputType.TYPE_TEXT_FLAG_CAP_SENTENCES); + l.addView(reason); + + final EditText note = new EditText(mContext); + note.setHint(R.string.mod_ban_note_mod); + note.setText(nt); + note.setInputType(InputType.TYPE_TEXT_FLAG_CAP_SENTENCES); + l.addView(note); + + final EditText message = new EditText(mContext); + message.setHint(R.string.mod_ban_note_user); + message.setText(msg); + message.setInputType(InputType.TYPE_TEXT_FLAG_CAP_SENTENCES); + l.addView(message); + + final EditText time = new EditText(mContext); + time.setHint(R.string.mod_ban_time); + time.setText(t); + time.setInputType(InputType.TYPE_CLASS_NUMBER); + l.addView(time); + + new AlertDialog.Builder(mContext) + .setView(l) + .setTitle(mContext.getString(R.string.mod_ban_title, submission.getAuthor())) + .setCancelable(true) + .setPositiveButton( + R.string.mod_btn_ban, + (dialog, which) -> { + // to ban + if (reason.getText().toString().isEmpty()) { + new AlertDialog.Builder(mContext) + .setTitle(R.string.mod_ban_reason_required) + .setMessage(R.string.misc_please_try_again) + .setPositiveButton( + R.string.btn_ok, + (dialog1, which1) -> + showBan(mContext, mToolbar, submission, reason.getText().toString(), note.getText().toString(), message.getText().toString(), time.getText().toString())) + .setCancelable(false) + .show(); + } else { + new AsyncTask() { + @Override + protected Boolean doInBackground(Void... params) { + try { + String n = note.getText().toString(); + String m = message.getText().toString(); + + if (n.isEmpty()) { + n = null; + } + if (m.isEmpty()) { + m = null; + } + if (time.getText().toString().isEmpty()) { + new ModerationManager(Authentication.reddit) + .banUserPermanently(submission.getSubredditName(), submission.getAuthor(), reason.getText().toString(), n, m); + } else { + new ModerationManager(Authentication.reddit) + .banUser(submission.getSubredditName(), submission.getAuthor(), reason.getText().toString(), n, m, Integer.parseInt(time.getText().toString())); + } + return true; + } catch (Exception e) { + if (e instanceof InvalidScopeException) { + scope = true; + } + e.printStackTrace(); + return false; + } + } + + boolean scope; + + @Override + protected void onPostExecute(Boolean done) { + Snackbar s; + if (done) { + s = Snackbar.make(mToolbar, R.string.mod_ban_success, Snackbar.LENGTH_SHORT); + } else { + if (scope) { + new AlertDialog.Builder(mContext) + .setTitle(R.string.mod_ban_reauth) + .setMessage(R.string.mod_ban_reauth_question) + .setPositiveButton( + R.string.btn_ok, + (dialog12, which12) -> { + Intent i = new Intent(mContext, Reauthenticate.class); + mContext.startActivity(i); + }) + .setNegativeButton( + R.string.misc_maybe_later, null) + .setCancelable(false) + .show(); + } + s = Snackbar.make(mToolbar, R.string.mod_ban_fail, Snackbar.LENGTH_INDEFINITE) + .setAction( + R.string.misc_try_again, + new View.OnClickListener() { + @Override + public void onClick( + View v) { + showBan( + mContext, + mToolbar, + submission, + reason.getText() + .toString(), + note.getText() + .toString(), + message.getText() + .toString(), + time.getText() + .toString()); + } + }); + } + + if (s != null) { + LayoutUtils.showSnackbar(s); + } + } + }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + }).setNegativeButton(R.string.btn_cancel, null).show(); + } +} \ 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 2c03384a8..8e8631a73 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1312,4 +1312,16 @@ Display a thin colored border around some dialog boxes. Failed to sync subreddits. No subscribed subreddits? + + Distinguish + Undistingiush + + NSFW status removed + NSFW status set + + Spoiler status removed + Spoiler status set + + Submission distinguished + Submission distinguish removed From 93606b9b86fe9864b9c7953ddb9631a98072400a Mon Sep 17 00:00:00 2001 From: Nathan Grennan Date: Mon, 31 Mar 2025 23:28:26 -0700 Subject: [PATCH 14/99] Broke AnimationBuilder class out of SubsamplingScaleImageView.java --- .../redditslide/Views/AnimationBuilder.java | 176 +++++++++++++ .../Views/SubsamplingScaleImageView.java | 236 +++--------------- 2 files changed, 208 insertions(+), 204 deletions(-) create mode 100644 app/src/main/java/me/edgan/redditslide/Views/AnimationBuilder.java diff --git a/app/src/main/java/me/edgan/redditslide/Views/AnimationBuilder.java b/app/src/main/java/me/edgan/redditslide/Views/AnimationBuilder.java new file mode 100644 index 000000000..3fa01e3db --- /dev/null +++ b/app/src/main/java/me/edgan/redditslide/Views/AnimationBuilder.java @@ -0,0 +1,176 @@ +package me.edgan.redditslide.Views; + +import android.graphics.PointF; +import android.util.Log; +import androidx.annotation.NonNull; + +import java.util.Arrays; +import java.util.List; + +/** + * Builder class used to set additional options for a scale animation. Create an instance using + * {@link SubsamplingScaleImageView#animateScale(float)}, then set your options and call {@link #start()}. + */ +public final class AnimationBuilder { + + private static final String TAG = AnimationBuilder.class.getSimpleName(); + + private final SubsamplingScaleImageView view; + private final float targetScale; + private final PointF targetSCenter; + private final PointF vFocus; + private long duration = 500; + private int easing = SubsamplingScaleImageView.EASE_IN_OUT_QUAD; + private int origin = SubsamplingScaleImageView.ORIGIN_ANIM; + private boolean interruptible = true; + private boolean panLimited = true; + private SubsamplingScaleImageView.OnAnimationEventListener listener; + + // Reference static constants via class + private static final List VALID_EASING_STYLES = + Arrays.asList(SubsamplingScaleImageView.EASE_IN_OUT_QUAD, SubsamplingScaleImageView.EASE_OUT_QUAD); + + + AnimationBuilder(SubsamplingScaleImageView view, PointF sCenter) { + this.view = view; + this.targetScale = view.scale; // Access via view instance + this.targetSCenter = sCenter; + this.vFocus = null; + } + + AnimationBuilder(SubsamplingScaleImageView view, float scale) { + this.view = view; + this.targetScale = scale; + this.targetSCenter = view.getCenter(); // Access via view instance + this.vFocus = null; + } + + AnimationBuilder(SubsamplingScaleImageView view, float scale, PointF sCenter) { + this.view = view; + this.targetScale = scale; + this.targetSCenter = sCenter; + this.vFocus = null; + } + + AnimationBuilder(SubsamplingScaleImageView view, float scale, PointF sCenter, PointF vFocus) { + this.view = view; + this.targetScale = scale; + this.targetSCenter = sCenter; + this.vFocus = vFocus; + } + + /** + * Desired duration of the anim in milliseconds. Default is 500. + * + * @param duration duration in milliseconds. + * @return this builder for method chaining. + */ + @NonNull + public AnimationBuilder withDuration(long duration) { + this.duration = duration; + return this; + } + + /** + * Whether the animation can be interrupted with a touch. Default is true. + * + * @param interruptible interruptible flag. + * @return this builder for method chaining. + */ + @NonNull + public AnimationBuilder withInterruptible(boolean interruptible) { + this.interruptible = interruptible; + return this; + } + + /** + * Set the easing style. See static fields. {@link SubsamplingScaleImageView#EASE_IN_OUT_QUAD} is recommended, and + * the default. + * + * @param easing easing style. + * @return this builder for method chaining. + */ + @NonNull + public AnimationBuilder withEasing(int easing) { + if (!VALID_EASING_STYLES.contains(easing)) { + throw new IllegalArgumentException("Unknown easing type: " + easing); + } + this.easing = easing; + return this; + } + + /** + * Add an animation event listener. + * + * @param listener The listener. + * @return this builder for method chaining. + */ + @NonNull + public AnimationBuilder withOnAnimationEventListener(SubsamplingScaleImageView.OnAnimationEventListener listener) { + this.listener = listener; + return this; + } + + /** + * Only for internal use. When set to true, the animation proceeds towards the actual end + * point - the nearest point to the center allowed by pan limits. When false, animation is + * in the direction of the requested end point and is stopped when the limit for each axis + * is reached. The latter behaviour is used for flings but nothing else. + */ + @NonNull + AnimationBuilder withPanLimited(boolean panLimited) { + this.panLimited = panLimited; + return this; + } + + /** Only for internal use. Indicates what caused the animation. */ + @NonNull + AnimationBuilder withOrigin(int origin) { + this.origin = origin; + return this; + } + + /** Starts the animation. */ + public void start() { + if (view.anim != null && view.anim.listener != null) { + try { + view.anim.listener.onInterruptedByNewAnim(); + } catch (Exception e) { + Log.w(TAG, "Error thrown by animation listener", e); + } + } + + int vxCenter = view.getPaddingLeft() + (view.getWidth() - view.getPaddingRight() - view.getPaddingLeft()) / 2; + int vyCenter = view.getPaddingTop() + (view.getHeight() - view.getPaddingBottom() - view.getPaddingTop()) / 2; + float targetScale = view.limitedScale(this.targetScale); + PointF targetSCenter = panLimited ? view.limitedSCenter(this.targetSCenter.x, this.targetSCenter.y, targetScale, new PointF()): this.targetSCenter; + view.anim = new SubsamplingScaleImageView.Anim(); + view.anim.scaleStart = view.scale; + view.anim.scaleEnd = targetScale; + view.anim.time = System.currentTimeMillis(); + view.anim.sCenterEndRequested = targetSCenter; + view.anim.sCenterStart = view.getCenter(); + view.anim.sCenterEnd = targetSCenter; + view.anim.vFocusStart = view.sourceToViewCoord(targetSCenter); + view.anim.vFocusEnd = new PointF(vxCenter, vyCenter); + view.anim.duration = duration; + view.anim.interruptible = interruptible; + view.anim.easing = easing; + view.anim.origin = origin; + view.anim.time = System.currentTimeMillis(); + view.anim.listener = listener; + + if (vFocus != null) { + // Calculate where translation will be at the end of the anim + float vTranslateXEnd = vFocus.x - (targetScale * view.anim.sCenterStart.x); + float vTranslateYEnd = vFocus.y - (targetScale * view.anim.sCenterStart.y); + SubsamplingScaleImageView.ScaleAndTranslate satEnd = new SubsamplingScaleImageView.ScaleAndTranslate(targetScale, new PointF(vTranslateXEnd, vTranslateYEnd)); + // Fit the end translation into bounds + view.fitToBounds(true, satEnd); + // Adjust the position of the focus point at end so image will be in bounds + view.anim.vFocusEnd = new PointF(vFocus.x + (satEnd.vTranslate.x - vTranslateXEnd), vFocus.y + (satEnd.vTranslate.y - vTranslateYEnd)); + } + + view.invalidate(); + } +} \ No newline at end of file diff --git a/app/src/main/java/me/edgan/redditslide/Views/SubsamplingScaleImageView.java b/app/src/main/java/me/edgan/redditslide/Views/SubsamplingScaleImageView.java index 4a1c7de84..ad2e6aa9d 100644 --- a/app/src/main/java/me/edgan/redditslide/Views/SubsamplingScaleImageView.java +++ b/app/src/main/java/me/edgan/redditslide/Views/SubsamplingScaleImageView.java @@ -319,7 +319,7 @@ public class SubsamplingScaleImageView extends View { private PointF quickScaleVStart; // Scale and center animation tracking - private Anim anim; + Anim anim; // Whether a ready notification has been sent to subclasses private boolean readySent; @@ -347,7 +347,7 @@ public class SubsamplingScaleImageView extends View { private Paint tileBgPaint; // Volatile fields used to reduce object creation - private ScaleAndTranslate satTemp; + ScaleAndTranslate satTemp; private Matrix matrix; private RectF sRect; private final float[] srcArray = new float[8]; @@ -727,7 +727,7 @@ public boolean onFling( ((getWidth() / 2.0f) - vTranslateEnd.x) / scale; float sCenterYEnd = ((getHeight() / 2.0f) - vTranslateEnd.y) / scale; - new AnimationBuilder(new PointF(sCenterXEnd, sCenterYEnd)) + new AnimationBuilder(SubsamplingScaleImageView.this, new PointF(sCenterXEnd, sCenterYEnd)) .withEasing(EASE_OUT_QUAD) .withPanLimited(false) .withOrigin(ORIGIN_FLING) @@ -1181,13 +1181,13 @@ private void doubleTapZoom(PointF sCenter, PointF vFocus) { if (doubleTapZoomStyle == ZOOM_FOCUS_CENTER_IMMEDIATE) { setScaleAndCenter(targetScale, sCenter); } else if (doubleTapZoomStyle == ZOOM_FOCUS_CENTER || !zoomIn || !panEnabled) { - new AnimationBuilder(targetScale, sCenter) + new AnimationBuilder(this, targetScale, sCenter) .withInterruptible(false) .withDuration(doubleTapZoomDuration) .withOrigin(ORIGIN_DOUBLE_TAP_ZOOM) .start(); } else if (doubleTapZoomStyle == ZOOM_FOCUS_FIXED) { - new AnimationBuilder(targetScale, sCenter, vFocus) + new AnimationBuilder(this, targetScale, sCenter, vFocus) .withInterruptible(false) .withDuration(doubleTapZoomDuration) .withOrigin(ORIGIN_DOUBLE_TAP_ZOOM) @@ -1798,7 +1798,7 @@ private int calculateInSampleSize(float scale) { * @param sat The scale we want and the translation we're aiming for. The values are adjusted to * be valid. */ - private void fitToBounds(boolean center, ScaleAndTranslate sat) { + void fitToBounds(boolean center, ScaleAndTranslate sat) { if (panLimit == PAN_LIMIT_OUTSIDE && isReady()) { center = false; } @@ -1856,7 +1856,7 @@ private void fitToBounds(boolean center, ScaleAndTranslate sat) { * @param center Whether the image should be centered in the dimension it's too small to fill. * While animating this can be false to avoid changes in direction as bounds are reached. */ - private void fitToBounds(boolean center) { + void fitToBounds(boolean center) { boolean init = false; if (vTranslate == null) { init = true; @@ -2348,32 +2348,30 @@ private static class Tile { private Rect fileSRect; } - private static class Anim { - - private float scaleStart; // Scale at start of anim - private float scaleEnd; // Scale at end of anim (target) - private PointF sCenterStart; // Source center point at start - private PointF sCenterEnd; // Source center point at end, adjusted for pan limits - private PointF - sCenterEndRequested; // Source center point that was requested, without adjustment - private PointF vFocusStart; // View point that was double tapped - private PointF vFocusEnd; // Where the view focal point should be moved to during the anim - private long duration = 500; // How long the anim takes - private boolean interruptible = true; // Whether the anim can be interrupted by a touch - private int easing = EASE_IN_OUT_QUAD; // Easing style - private int origin = ORIGIN_ANIM; // Animation origin (API, double tap or fling) - private long time = System.currentTimeMillis(); // Start time - private OnAnimationEventListener listener; // Event listener - } - - private static class ScaleAndTranslate { - private ScaleAndTranslate(float scale, PointF vTranslate) { + static class Anim { + float scaleStart; // Scale at start of anim + float scaleEnd; // Scale at end of anim (target) + PointF sCenterStart; // Source center point at start + PointF sCenterEnd; // Source center point at end, adjusted for pan limits + PointF sCenterEndRequested; // Source center point that was requested, without adjustment + PointF vFocusStart; // View point that was double tapped + PointF vFocusEnd; // Where the view focal point should be moved to during the anim + long duration = 500; // How long the anim takes + boolean interruptible = true; // Whether the anim can be interrupted by a touch + int easing = EASE_IN_OUT_QUAD; // Easing style + int origin = ORIGIN_ANIM; // Animation origin (API, double tap or fling) + long time = System.currentTimeMillis(); // Start time + OnAnimationEventListener listener; // Event listener + } + + static class ScaleAndTranslate { + ScaleAndTranslate(float scale, PointF vTranslate) { this.scale = scale; this.vTranslate = vTranslate; } - private float scale; - private final PointF vTranslate; + float scale; + final PointF vTranslate; } /** Set scale, center and orientation from saved state. */ @@ -2718,7 +2716,7 @@ private PointF vTranslateForSCenter(float sCenterX, float sCenterY, float scale) * screen as allowed. */ @NonNull - private PointF limitedSCenter( + PointF limitedSCenter( float sCenterX, float sCenterY, float scale, @NonNull PointF sTarget) { PointF vTranslate = vTranslateForSCenter(sCenterX, sCenterY, scale); int vxCenter = getPaddingLeft() + (getWidth() - getPaddingRight() - getPaddingLeft()) / 2; @@ -2747,7 +2745,7 @@ private float minScale() { } /** Adjust a requested scale to be within the allowed limits. */ - private float limitedScale(float targetScale) { + float limitedScale(float targetScale) { targetScale = Math.max(minScale(), targetScale); targetScale = Math.min(maxScale, targetScale); return targetScale; @@ -3415,7 +3413,7 @@ public AnimationBuilder animateCenter(PointF sCenter) { if (!isReady()) { return null; } - return new AnimationBuilder(sCenter); + return new AnimationBuilder(this, sCenter); } /** @@ -3432,7 +3430,7 @@ public AnimationBuilder animateScale(float scale) { if (!isReady()) { return null; } - return new AnimationBuilder(scale); + return new AnimationBuilder(this, scale); } /** @@ -3450,177 +3448,7 @@ public AnimationBuilder animateScaleAndCenter(float scale, PointF sCenter) { if (!isReady()) { return null; } - return new AnimationBuilder(scale, sCenter); - } - - /** - * Builder class used to set additional options for a scale animation. Create an instance using - * {@link #animateScale(float)}, then set your options and call {@link #start()}. - */ - public final class AnimationBuilder { - - private final float targetScale; - private final PointF targetSCenter; - private final PointF vFocus; - private long duration = 500; - private int easing = EASE_IN_OUT_QUAD; - private int origin = ORIGIN_ANIM; - private boolean interruptible = true; - private boolean panLimited = true; - private OnAnimationEventListener listener; - - private AnimationBuilder(PointF sCenter) { - this.targetScale = scale; - this.targetSCenter = sCenter; - this.vFocus = null; - } - - private AnimationBuilder(float scale) { - this.targetScale = scale; - this.targetSCenter = getCenter(); - this.vFocus = null; - } - - private AnimationBuilder(float scale, PointF sCenter) { - this.targetScale = scale; - this.targetSCenter = sCenter; - this.vFocus = null; - } - - private AnimationBuilder(float scale, PointF sCenter, PointF vFocus) { - this.targetScale = scale; - this.targetSCenter = sCenter; - this.vFocus = vFocus; - } - - /** - * Desired duration of the anim in milliseconds. Default is 500. - * - * @param duration duration in milliseconds. - * @return this builder for method chaining. - */ - @NonNull - public AnimationBuilder withDuration(long duration) { - this.duration = duration; - return this; - } - - /** - * Whether the animation can be interrupted with a touch. Default is true. - * - * @param interruptible interruptible flag. - * @return this builder for method chaining. - */ - @NonNull - public AnimationBuilder withInterruptible(boolean interruptible) { - this.interruptible = interruptible; - return this; - } - - /** - * Set the easing style. See static fields. {@link #EASE_IN_OUT_QUAD} is recommended, and - * the default. - * - * @param easing easing style. - * @return this builder for method chaining. - */ - @NonNull - public AnimationBuilder withEasing(int easing) { - if (!VALID_EASING_STYLES.contains(easing)) { - throw new IllegalArgumentException("Unknown easing type: " + easing); - } - this.easing = easing; - return this; - } - - /** - * Add an animation event listener. - * - * @param listener The listener. - * @return this builder for method chaining. - */ - @NonNull - public AnimationBuilder withOnAnimationEventListener(OnAnimationEventListener listener) { - this.listener = listener; - return this; - } - - /** - * Only for internal use. When set to true, the animation proceeds towards the actual end - * point - the nearest point to the center allowed by pan limits. When false, animation is - * in the direction of the requested end point and is stopped when the limit for each axis - * is reached. The latter behaviour is used for flings but nothing else. - */ - @NonNull - private AnimationBuilder withPanLimited(boolean panLimited) { - this.panLimited = panLimited; - return this; - } - - /** Only for internal use. Indicates what caused the animation. */ - @NonNull - private AnimationBuilder withOrigin(int origin) { - this.origin = origin; - return this; - } - - /** Starts the animation. */ - public void start() { - if (anim != null && anim.listener != null) { - try { - anim.listener.onInterruptedByNewAnim(); - } catch (Exception e) { - Log.w(TAG, "Error thrown by animation listener", e); - } - } - - int vxCenter = - getPaddingLeft() + (getWidth() - getPaddingRight() - getPaddingLeft()) / 2; - int vyCenter = - getPaddingTop() + (getHeight() - getPaddingBottom() - getPaddingTop()) / 2; - float targetScale = limitedScale(this.targetScale); - PointF targetSCenter = - panLimited - ? limitedSCenter( - this.targetSCenter.x, - this.targetSCenter.y, - targetScale, - new PointF()) - : this.targetSCenter; - anim = new Anim(); - anim.scaleStart = scale; - anim.scaleEnd = targetScale; - anim.time = System.currentTimeMillis(); - anim.sCenterEndRequested = targetSCenter; - anim.sCenterStart = getCenter(); - anim.sCenterEnd = targetSCenter; - anim.vFocusStart = sourceToViewCoord(targetSCenter); - anim.vFocusEnd = new PointF(vxCenter, vyCenter); - anim.duration = duration; - anim.interruptible = interruptible; - anim.easing = easing; - anim.origin = origin; - anim.time = System.currentTimeMillis(); - anim.listener = listener; - - if (vFocus != null) { - // Calculate where translation will be at the end of the anim - float vTranslateXEnd = vFocus.x - (targetScale * anim.sCenterStart.x); - float vTranslateYEnd = vFocus.y - (targetScale * anim.sCenterStart.y); - ScaleAndTranslate satEnd = - new ScaleAndTranslate( - targetScale, new PointF(vTranslateXEnd, vTranslateYEnd)); - // Fit the end translation into bounds - fitToBounds(true, satEnd); - // Adjust the position of the focus point at end so image will be in bounds - anim.vFocusEnd = - new PointF( - vFocus.x + (satEnd.vTranslate.x - vTranslateXEnd), - vFocus.y + (satEnd.vTranslate.y - vTranslateYEnd)); - } - - invalidate(); - } + return new AnimationBuilder(this, scale, sCenter); } /** From 13a75b6bb030fcbcec57e12885b33d2a0a5fd68b Mon Sep 17 00:00:00 2001 From: Nathan Grennan Date: Tue, 1 Apr 2025 00:15:59 -0700 Subject: [PATCH 15/99] Fixed formatting of SubsamplingScaleImageView.java --- .../Views/SubsamplingScaleImageView.java | 541 +++++++++--------- 1 file changed, 259 insertions(+), 282 deletions(-) diff --git a/app/src/main/java/me/edgan/redditslide/Views/SubsamplingScaleImageView.java b/app/src/main/java/me/edgan/redditslide/Views/SubsamplingScaleImageView.java index ad2e6aa9d..3a95c5a21 100644 --- a/app/src/main/java/me/edgan/redditslide/Views/SubsamplingScaleImageView.java +++ b/app/src/main/java/me/edgan/redditslide/Views/SubsamplingScaleImageView.java @@ -1,7 +1,5 @@ package me.edgan.redditslide.Views; -// Manually ported to 3.10.0 - import android.content.ContentResolver; import android.content.Context; import android.content.res.TypedArray; @@ -96,13 +94,13 @@ public class SubsamplingScaleImageView extends View { /** Rotate the image 270 degrees clockwise. */ public static final int ORIENTATION_270 = 270; - private static final List VALID_ORIENTATIONS = - Arrays.asList( - ORIENTATION_0, - ORIENTATION_90, - ORIENTATION_180, - ORIENTATION_270, - ORIENTATION_USE_EXIF); + private static final List VALID_ORIENTATIONS = Arrays.asList( + ORIENTATION_0, + ORIENTATION_90, + ORIENTATION_180, + ORIENTATION_270, + ORIENTATION_USE_EXIF + ); /** * During zoom animation, keep the point of the image that was tapped in the same place, and @@ -119,8 +117,7 @@ public class SubsamplingScaleImageView extends View { /** Zoom in to and center the tapped point immediately without animating. */ public static final int ZOOM_FOCUS_CENTER_IMMEDIATE = 3; - private static final List VALID_ZOOM_STYLES = - Arrays.asList(ZOOM_FOCUS_FIXED, ZOOM_FOCUS_CENTER, ZOOM_FOCUS_CENTER_IMMEDIATE); + private static final List VALID_ZOOM_STYLES = Arrays.asList(ZOOM_FOCUS_FIXED, ZOOM_FOCUS_CENTER, ZOOM_FOCUS_CENTER_IMMEDIATE); /** Quadratic ease out. Not recommended for scale animation, but good for panning. */ public static final int EASE_OUT_QUAD = 1; @@ -128,8 +125,7 @@ public class SubsamplingScaleImageView extends View { /** Quadratic ease in and out. */ public static final int EASE_IN_OUT_QUAD = 2; - private static final List VALID_EASING_STYLES = - Arrays.asList(EASE_IN_OUT_QUAD, EASE_OUT_QUAD); + private static final List VALID_EASING_STYLES = Arrays.asList(EASE_IN_OUT_QUAD, EASE_OUT_QUAD); /** * Don't allow the image to be panned off screen. As much of the image as possible is always @@ -149,8 +145,7 @@ public class SubsamplingScaleImageView extends View { */ public static final int PAN_LIMIT_CENTER = 3; - private static final List VALID_PAN_LIMITS = - Arrays.asList(PAN_LIMIT_INSIDE, PAN_LIMIT_OUTSIDE, PAN_LIMIT_CENTER); + private static final List VALID_PAN_LIMITS = Arrays.asList(PAN_LIMIT_INSIDE, PAN_LIMIT_OUTSIDE, PAN_LIMIT_CENTER); /** * Scale the image so that both dimensions of the image will be equal to or less than the @@ -177,12 +172,12 @@ public class SubsamplingScaleImageView extends View { */ public static final int SCALE_TYPE_START = 4; - private static final List VALID_SCALE_TYPES = - Arrays.asList( - SCALE_TYPE_CENTER_CROP, - SCALE_TYPE_CENTER_INSIDE, - SCALE_TYPE_CUSTOM, - SCALE_TYPE_START); + private static final List VALID_SCALE_TYPES = Arrays.asList( + SCALE_TYPE_CENTER_CROP, + SCALE_TYPE_CENTER_INSIDE, + SCALE_TYPE_CUSTOM, + SCALE_TYPE_START + ); /** State change originated from animation. */ public static final int ORIGIN_ANIM = 1; @@ -386,46 +381,41 @@ public boolean handleMessage(Message message) { TypedArray typedAttr = getContext().obtainStyledAttributes(attr, styleable.SubsamplingScaleImageView); if (typedAttr.hasValue(styleable.SubsamplingScaleImageView_assetName)) { - String assetName = - typedAttr.getString(styleable.SubsamplingScaleImageView_assetName); + String assetName = typedAttr.getString(styleable.SubsamplingScaleImageView_assetName); + if (assetName != null && assetName.length() > 0) { setImage(ImageSource.asset(assetName).tilingEnabled()); } } + if (typedAttr.hasValue(styleable.SubsamplingScaleImageView_src)) { int resId = typedAttr.getResourceId(styleable.SubsamplingScaleImageView_src, 0); + if (resId > 0) { setImage(ImageSource.resource(resId).tilingEnabled()); } } + if (typedAttr.hasValue(styleable.SubsamplingScaleImageView_panEnabled)) { - setPanEnabled( - typedAttr.getBoolean(styleable.SubsamplingScaleImageView_panEnabled, true)); + setPanEnabled(typedAttr.getBoolean(styleable.SubsamplingScaleImageView_panEnabled, true)); } + if (typedAttr.hasValue(styleable.SubsamplingScaleImageView_zoomEnabled)) { - setZoomEnabled( - typedAttr.getBoolean( - styleable.SubsamplingScaleImageView_zoomEnabled, true)); + setZoomEnabled(typedAttr.getBoolean(styleable.SubsamplingScaleImageView_zoomEnabled, true)); } + if (typedAttr.hasValue(styleable.SubsamplingScaleImageView_quickScaleEnabled)) { - setQuickScaleEnabled( - typedAttr.getBoolean( - styleable.SubsamplingScaleImageView_quickScaleEnabled, true)); + setQuickScaleEnabled(typedAttr.getBoolean(styleable.SubsamplingScaleImageView_quickScaleEnabled, true)); } + if (typedAttr.hasValue(styleable.SubsamplingScaleImageView_tileBackgroundColor)) { - setTileBackgroundColor( - typedAttr.getColor( - styleable.SubsamplingScaleImageView_tileBackgroundColor, - Color.argb(0, 0, 0, 0))); + setTileBackgroundColor(typedAttr.getColor(styleable.SubsamplingScaleImageView_tileBackgroundColor, Color.argb(0, 0, 0, 0))); } + typedAttr.recycle(); } - quickScaleThreshold = - TypedValue.applyDimension( - TypedValue.COMPLEX_UNIT_DIP, - 20, - context.getResources().getDisplayMetrics()); + quickScaleThreshold = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 20, context.getResources().getDisplayMetrics()); } public SubsamplingScaleImageView(Context context) { @@ -540,34 +530,26 @@ public final void setImage( if (previewSource != null) { if (imageSource.getBitmap() != null) { - throw new IllegalArgumentException( - "Preview image cannot be used when a bitmap is provided for the main" - + " image"); + throw new IllegalArgumentException("Preview image cannot be used when a bitmap is provided for the main image"); } + if (imageSource.getSWidth() <= 0 || imageSource.getSHeight() <= 0) { - throw new IllegalArgumentException( - "Preview image cannot be used unless dimensions are provided for the main" - + " image"); + throw new IllegalArgumentException("Preview image cannot be used unless dimensions are provided for the main image"); } + this.sWidth = imageSource.getSWidth(); this.sHeight = imageSource.getSHeight(); this.pRegion = previewSource.getSRegion(); + if (previewSource.getBitmap() != null) { this.bitmapIsCached = previewSource.isCached(); onPreviewLoaded(previewSource.getBitmap()); } else { Uri uri = previewSource.getUri(); if (uri == null && previewSource.getResource() != null) { - uri = - Uri.parse( - ContentResolver.SCHEME_ANDROID_RESOURCE - + "://" - + getContext().getPackageName() - + "/" - + previewSource.getResource()); + uri = Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://" + getContext().getPackageName() + "/" + previewSource.getResource()); } - BitmapLoadTask task = - new BitmapLoadTask(this, getContext(), bitmapDecoderFactory, uri, true); + BitmapLoadTask task = new BitmapLoadTask(this, getContext(), bitmapDecoderFactory, uri, true); execute(task); } } @@ -587,15 +569,11 @@ public final void setImage( } else { sRegion = imageSource.getSRegion(); uri = imageSource.getUri(); + if (uri == null && imageSource.getResource() != null) { - uri = - Uri.parse( - ContentResolver.SCHEME_ANDROID_RESOURCE - + "://" - + getContext().getPackageName() - + "/" - + imageSource.getResource()); + uri = Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://" + getContext().getPackageName() + "/" + imageSource.getResource()); } + doLoader(imageSource, false); } } @@ -608,22 +586,20 @@ public void doLoader(boolean rapid) { public void doLoader(ImageSource imageSource, boolean rapid) { if (imageSource.getTile() || sRegion != null) { + // Load the bitmap using tile decoding. if (rapid) { setRegionDecoderClass(RapidImageRegionDecoder.class); - TilesInitTask task = - new TilesInitTask(this, getContext(), regionDecoderFactory, uri); + TilesInitTask task = new TilesInitTask(this, getContext(), regionDecoderFactory, uri); execute(task); } else { savedImageSource = imageSource; - TilesInitTask task = - new TilesInitTask(this, getContext(), regionDecoderFactory, uri); + TilesInitTask task = new TilesInitTask(this, getContext(), regionDecoderFactory, uri); execute(task); } } else { // Load the bitmap as a single image. - BitmapLoadTask task = - new BitmapLoadTask(this, getContext(), bitmapDecoderFactory, uri, false); + BitmapLoadTask task = new BitmapLoadTask(this, getContext(), bitmapDecoderFactory, uri, false); execute(task); } } @@ -666,12 +642,15 @@ private void reset(boolean newImage) { } finally { decoderLock.writeLock().unlock(); } + if (bitmap != null && !bitmapIsCached) { bitmap.recycle(); } + if (bitmap != null && bitmapIsCached && onImageEventListener != null) { onImageEventListener.onPreviewReleased(); } + sWidth = 0; sHeight = 0; sOrientation = 0; @@ -683,6 +662,7 @@ private void reset(boolean newImage) { bitmapIsPreview = false; bitmapIsCached = false; } + if (tileMap != null) { for (Map.Entry> tileMapEntry : tileMap.entrySet()) { for (Tile tile : tileMapEntry.getValue()) { @@ -693,104 +673,95 @@ private void reset(boolean newImage) { } } } + tileMap = null; } + setGestureDetector(getContext()); } private void setGestureDetector(final Context context) { - this.detector = - new GestureDetector( - context, - new GestureDetector.SimpleOnGestureListener() { - - @Override - public boolean onFling( - MotionEvent e1, - MotionEvent e2, - float velocityX, - float velocityY) { - if (panEnabled - && readySent - && vTranslate != null - && e1 != null - && e2 != null - && (Math.abs(e1.getX() - e2.getX()) > 50 - || Math.abs(e1.getY() - e2.getY()) > 50) - && (Math.abs(velocityX) > 500 || Math.abs(velocityY) > 500) - && !isZooming) { - PointF vTranslateEnd = - new PointF( - vTranslate.x + (velocityX * 0.25f), - vTranslate.y + (velocityY * 0.25f)); - float sCenterXEnd = - ((getWidth() / 2.0f) - vTranslateEnd.x) / scale; - float sCenterYEnd = - ((getHeight() / 2.0f) - vTranslateEnd.y) / scale; - new AnimationBuilder(SubsamplingScaleImageView.this, new PointF(sCenterXEnd, sCenterYEnd)) - .withEasing(EASE_OUT_QUAD) - .withPanLimited(false) - .withOrigin(ORIGIN_FLING) - .start(); - return true; - } - return super.onFling(e1, e2, velocityX, velocityY); - } + this.detector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() { + @Override + public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { + if (panEnabled + && readySent + && vTranslate != null + && e1 != null + && e2 != null + && (Math.abs(e1.getX() - e2.getX()) > 50 + || Math.abs(e1.getY() - e2.getY()) > 50) + && (Math.abs(velocityX) > 500 || Math.abs(velocityY) > 500) + && !isZooming) { + PointF vTranslateEnd = + new PointF( + vTranslate.x + (velocityX * 0.25f), + vTranslate.y + (velocityY * 0.25f)); + float sCenterXEnd = + ((getWidth() / 2.0f) - vTranslateEnd.x) / scale; + float sCenterYEnd = + ((getHeight() / 2.0f) - vTranslateEnd.y) / scale; + new AnimationBuilder(SubsamplingScaleImageView.this, new PointF(sCenterXEnd, sCenterYEnd)) + .withEasing(EASE_OUT_QUAD) + .withPanLimited(false) + .withOrigin(ORIGIN_FLING) + .start(); + return true; + } + return super.onFling(e1, e2, velocityX, velocityY); + } - @Override - public boolean onSingleTapConfirmed(MotionEvent e) { - performClick(); - return true; - } + @Override + public boolean onSingleTapConfirmed(MotionEvent e) { + performClick(); + return true; + } - @Override - public boolean onDoubleTap(MotionEvent e) { - if (zoomEnabled && readySent && vTranslate != null) { - // Hacky solution for #15 - after a double tap the - // GestureDetector gets in a state - // where the next fling is ignored, so here we replace it with a - // new one. - setGestureDetector(context); - if (quickScaleEnabled) { - // Store quick scale params. This will become either a - // double tap zoom or a - // quick scale depending on whether the user swipes. - vCenterStart = new PointF(e.getX(), e.getY()); - vTranslateStart = new PointF(vTranslate.x, vTranslate.y); - scaleStart = scale; - isQuickScaling = true; - isZooming = true; - quickScaleLastDistance = -1F; - quickScaleSCenter = viewToSourceCoord(vCenterStart); - quickScaleVStart = new PointF(e.getX(), e.getY()); - quickScaleVLastPoint = - new PointF( - quickScaleSCenter.x, quickScaleSCenter.y); - quickScaleMoved = false; - // We need to get events in onTouchEvent after this. - return false; - } else { - // Start double tap zoom animation. - doubleTapZoom( - viewToSourceCoord(new PointF(e.getX(), e.getY())), - new PointF(e.getX(), e.getY())); - return true; - } - } - return super.onDoubleTapEvent(e); - } - }); + @Override + public boolean onDoubleTap(MotionEvent e) { + if (zoomEnabled && readySent && vTranslate != null) { + // Hacky solution for #15 - after a double tap the + // GestureDetector gets in a state + // where the next fling is ignored, so here we replace it with a + // new one. + setGestureDetector(context); + if (quickScaleEnabled) { + // Store quick scale params. This will become either a + // double tap zoom or a + // quick scale depending on whether the user swipes. + vCenterStart = new PointF(e.getX(), e.getY()); + vTranslateStart = new PointF(vTranslate.x, vTranslate.y); + scaleStart = scale; + isQuickScaling = true; + isZooming = true; + quickScaleLastDistance = -1F; + quickScaleSCenter = viewToSourceCoord(vCenterStart); + quickScaleVStart = new PointF(e.getX(), e.getY()); + quickScaleVLastPoint = + new PointF( + quickScaleSCenter.x, quickScaleSCenter.y); + quickScaleMoved = false; + // We need to get events in onTouchEvent after this. + return false; + } else { + // Start double tap zoom animation. + doubleTapZoom( + viewToSourceCoord(new PointF(e.getX(), e.getY())), + new PointF(e.getX(), e.getY())); + return true; + } + } + return super.onDoubleTapEvent(e); + } + }); - singleDetector = - new GestureDetector( - context, - new GestureDetector.SimpleOnGestureListener() { - @Override - public boolean onSingleTapConfirmed(MotionEvent e) { - performClick(); - return true; - } - }); + singleDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() { + @Override + public boolean onSingleTapConfirmed(MotionEvent e) { + performClick(); + return true; + } + }); } /** @@ -823,6 +794,7 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { boolean resizeHeight = heightSpecMode != MeasureSpec.EXACTLY; int width = parentWidth; int height = parentHeight; + if (sWidth > 0 && sHeight > 0) { if (resizeWidth && resizeHeight) { width = sWidth(); @@ -833,6 +805,7 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { width = (int) ((((double) sWidth() / (double) sHeight()) * height)); } } + width = Math.max(width, getSuggestedMinimumWidth()); height = Math.max(height, getSuggestedMinimumHeight()); setMeasuredDimension(width, height); @@ -863,6 +836,7 @@ public boolean onTouchEvent(@NonNull MotionEvent event) { } return true; } + // Detect flings, taps and double taps if (!isQuickScaling && (detector == null || detector.onTouchEvent(event))) { isZooming = false; @@ -874,9 +848,11 @@ public boolean onTouchEvent(@NonNull MotionEvent event) { if (vTranslateStart == null) { vTranslateStart = new PointF(0, 0); } + if (vTranslateBefore == null) { vTranslateBefore = new PointF(0, 0); } + if (vCenterStart == null) { vCenterStart = new PointF(0, 0); } @@ -900,6 +876,7 @@ private boolean onTouchEventInternal(@NonNull MotionEvent event) { anim = null; requestDisallowInterceptTouchEvent(true); maxTouchCount = Math.max(maxTouchCount, touchCount); + if (touchCount >= 2) { if (zoomEnabled) { // Start pinch to zoom. Calculate distance between touch points and center @@ -927,9 +904,11 @@ private boolean onTouchEventInternal(@NonNull MotionEvent event) { // Start long click timer handler.sendEmptyMessageDelayed(MESSAGE_LONG_CLICK, 600); } + return true; case MotionEvent.ACTION_MOVE: boolean consumed = false; + if (maxTouchCount > 0) { if (touchCount >= 2) { // Calculate new distance between touch points, to scale and pan relative to @@ -1101,22 +1080,26 @@ private boolean onTouchEventInternal(@NonNull MotionEvent event) { } } } + if (consumed) { handler.removeMessages(MESSAGE_LONG_CLICK); invalidate(); return true; } + break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_POINTER_UP: case MotionEvent.ACTION_POINTER_2_UP: handler.removeMessages(MESSAGE_LONG_CLICK); + if (isQuickScaling) { isQuickScaling = false; if (!quickScaleMoved) { doubleTapZoom(quickScaleSCenter, vCenterStart); } } + if (maxTouchCount > 0 && (isZooming || isPanning)) { if (isZooming && touchCount == 2) { // Convert from zoom to pan with remaining touch @@ -1141,13 +1124,16 @@ private boolean onTouchEventInternal(@NonNull MotionEvent event) { refreshRequiredTiles(true); return true; } + if (touchCount == 1) { isZooming = false; isPanning = false; maxTouchCount = 0; } + return true; } + return false; } @@ -1174,10 +1160,11 @@ private void doubleTapZoom(PointF sCenter, PointF vFocus) { sCenter.y = sHeight() / 2.0f; } } - float doubleTapZoomScale = - Math.min(maxScale, SubsamplingScaleImageView.this.doubleTapZoomScale); + + float doubleTapZoomScale = Math.min(maxScale, this.doubleTapZoomScale); boolean zoomIn = (scale <= doubleTapZoomScale * 0.9) || scale == minScale; float targetScale = zoomIn ? doubleTapZoomScale : minScale(); + if (doubleTapZoomStyle == ZOOM_FOCUS_CENTER_IMMEDIATE) { setScaleAndCenter(targetScale, sCenter); } else if (doubleTapZoomStyle == ZOOM_FOCUS_CENTER || !zoomIn || !panEnabled) { @@ -1193,6 +1180,7 @@ private void doubleTapZoom(PointF sCenter, PointF vFocus) { .withOrigin(ORIGIN_DOUBLE_TAP_ZOOM) .start(); } + invalidate(); } @@ -1250,22 +1238,10 @@ protected void onDraw(Canvas canvas) { anim.duration); // Apply required animation to the focal point - float vFocusNowX = - ease( - anim.easing, - scaleElapsed, - anim.vFocusStart.x, - anim.vFocusEnd.x - anim.vFocusStart.x, - anim.duration); - float vFocusNowY = - ease( - anim.easing, - scaleElapsed, - anim.vFocusStart.y, - anim.vFocusEnd.y - anim.vFocusStart.y, - anim.duration); - // Find out where the focal point is at this scale and adjust its position to follow the - // animation path + float vFocusNowX = ease(anim.easing, scaleElapsed, anim.vFocusStart.x, anim.vFocusEnd.x - anim.vFocusStart.x, anim.duration); + float vFocusNowY = ease(anim.easing, scaleElapsed, anim.vFocusStart.y, anim.vFocusEnd.y - anim.vFocusStart.y, anim.duration); + + // Find out where the focal point is at this scale and adjust its position to follow the animation path vTranslate.x -= sourceToViewX(anim.sCenterEnd.x) - vFocusNowX; vTranslate.y -= sourceToViewY(anim.sCenterEnd.y) - vFocusNowY; @@ -1274,6 +1250,7 @@ protected void onDraw(Canvas canvas) { fitToBounds(finished || (anim.scaleStart == anim.scaleEnd)); sendStateChanged(scaleBefore, vTranslateBefore, anim.origin); refreshRequiredTiles(finished); + if (finished) { if (anim.listener != null) { try { @@ -1284,17 +1261,18 @@ protected void onDraw(Canvas canvas) { } anim = null; } + invalidate(); } if (tileMap != null && isBaseLayerReady()) { - // Optimum sample size for current scale int sampleSize = Math.min(fullImageSampleSize, calculateInSampleSize(scale)); // First check for missing tiles - if there are any we need the base layer underneath to // avoid gaps boolean hasMissingTiles = false; + for (Map.Entry> tileMapEntry : tileMap.entrySet()) { if (tileMapEntry.getKey() == sampleSize) { for (Tile tile : tileMapEntry.getValue()) { @@ -1406,10 +1384,10 @@ protected void onDraw(Canvas canvas) { } } } - } else if (bitmap != null && !bitmap.isRecycled()) { float xScale = scale, yScale = scale; + if (bitmapIsPreview) { xScale = scale * ((float) sWidth / bitmap.getWidth()); yScale = scale * ((float) sHeight / bitmap.getHeight()); @@ -1418,6 +1396,7 @@ protected void onDraw(Canvas canvas) { if (matrix == null) { matrix = new Matrix(); } + matrix.reset(); matrix.postScale(xScale, yScale); matrix.postRotate(getRequiredRotation()); @@ -1560,20 +1539,18 @@ private boolean isBaseLayerReady() { * display an image. */ private boolean checkReady() { - boolean ready = - getWidth() > 0 - && getHeight() > 0 - && sWidth > 0 - && sHeight > 0 - && (bitmap != null || isBaseLayerReady()); + boolean ready = getWidth() > 0 && getHeight() > 0 && sWidth > 0 && sHeight > 0 && (bitmap != null || isBaseLayerReady()); + if (!readySent && ready) { preDraw(); readySent = true; onReady(); + if (onImageEventListener != null) { onImageEventListener.onReady(); } } + return ready; } @@ -1583,6 +1560,7 @@ && getHeight() > 0 */ private boolean checkImageLoaded() { boolean imageLoaded = isBaseLayerReady(); + if (!imageLoadedSent && imageLoaded) { preDraw(); imageLoadedSent = true; @@ -1591,6 +1569,7 @@ private boolean checkImageLoaded() { onImageEventListener.onImageLoaded(); } } + return imageLoaded; } @@ -1602,6 +1581,7 @@ private void createPaints() { bitmapPaint.setFilterBitmap(true); bitmapPaint.setDither(true); } + if ((debugTextPaint == null || debugLinePaint == null) && debug) { debugTextPaint = new Paint(); debugTextPaint.setTextSize(px(12)); @@ -1619,9 +1599,7 @@ private void createPaints() { * starts async loading of the base layer image - the whole source subsampled as necessary. */ private synchronized void initialiseBaseLayer(@NonNull Point maxTileDimensions) { - debug( - "initialiseBaseLayer maxTileDimensions=%dx%d", - maxTileDimensions.x, maxTileDimensions.y); + debug("initialiseBaseLayer maxTileDimensions=%dx%d", maxTileDimensions.x, maxTileDimensions.y); satTemp = new ScaleAndTranslate(0f, new PointF(0, 0)); fitToBounds(true, satTemp); @@ -1630,15 +1608,12 @@ private synchronized void initialiseBaseLayer(@NonNull Point maxTileDimensions) // four are required, // so don't bother with tiling until the next level 16 tiles are needed. fullImageSampleSize = calculateInSampleSize(satTemp.scale); + if (fullImageSampleSize > 1) { fullImageSampleSize /= 2; } - if (fullImageSampleSize == 1 - && sRegion == null - && sWidth() < maxTileDimensions.x - && sHeight() < maxTileDimensions.y) { - + if (fullImageSampleSize == 1 && sRegion == null && sWidth() < maxTileDimensions.x && sHeight() < maxTileDimensions.y) { // Whole image is required at native resolution, and is smaller than the canvas max // bitmap size. // Use BitmapDecoder for better image support. @@ -1647,16 +1622,16 @@ && sHeight() < maxTileDimensions.y) { BitmapLoadTask task = new BitmapLoadTask(this, getContext(), bitmapDecoderFactory, uri, false); execute(task); - } else { - initialiseTileMap(maxTileDimensions); List baseGrid = tileMap.get(fullImageSampleSize); + for (Tile baseTile : baseGrid) { TileLoadTask task = new TileLoadTask(this, decoder, baseTile); execute(task); } + refreshRequiredTiles(true); } } @@ -1718,6 +1693,7 @@ private boolean tileVisible(Tile tile) { sVisRight = viewToSourceX(getWidth()), sVisTop = viewToSourceY(0), sVisBottom = viewToSourceY(getHeight()); + return !(sVisLeft > tile.sRect.right || tile.sRect.left > sVisRight || sVisTop > tile.sRect.bottom @@ -1820,17 +1796,12 @@ void fitToBounds(boolean center, ScaleAndTranslate sat) { } // Asymmetric padding adjustments - float xPaddingRatio = - getPaddingLeft() > 0 || getPaddingRight() > 0 - ? getPaddingLeft() / (float) (getPaddingLeft() + getPaddingRight()) - : 0.5f; - float yPaddingRatio = - getPaddingTop() > 0 || getPaddingBottom() > 0 - ? getPaddingTop() / (float) (getPaddingTop() + getPaddingBottom()) - : 0.5f; + float xPaddingRatio = getPaddingLeft() > 0 || getPaddingRight() > 0 ? getPaddingLeft() / (float) (getPaddingLeft() + getPaddingRight()) : 0.5f; + float yPaddingRatio = getPaddingTop() > 0 || getPaddingBottom() > 0 ? getPaddingTop() / (float) (getPaddingTop() + getPaddingBottom()) : 0.5f; float maxTx; float maxTy; + if (panLimit == PAN_LIMIT_CENTER && isReady()) { maxTx = Math.max(0, getWidth() / 2); maxTy = Math.max(0, getHeight() / 2); @@ -1858,13 +1829,16 @@ void fitToBounds(boolean center, ScaleAndTranslate sat) { */ void fitToBounds(boolean center) { boolean init = false; + if (vTranslate == null) { init = true; vTranslate = new PointF(0, 0); } + if (satTemp == null) { satTemp = new ScaleAndTranslate(0, new PointF(0, 0)); } + satTemp.scale = scale; satTemp.vTranslate.set(vTranslate); fitToBounds(center, satTemp); @@ -1879,9 +1853,7 @@ void fitToBounds(boolean center) { * Once source image and view dimensions are known, creates a map of sample size to tile grid. */ private void initialiseTileMap(Point maxTileDimensions) { - debug( - "initialiseTileMap maxTileDimensions=%dx%d", - maxTileDimensions.x, maxTileDimensions.y); + debug("initialiseTileMap maxTileDimensions=%dx%d", maxTileDimensions.x, maxTileDimensions.y); this.tileMap = new LinkedHashMap<>(); int sampleSize = fullImageSampleSize; int xTiles = 1; @@ -1891,19 +1863,23 @@ private void initialiseTileMap(Point maxTileDimensions) { int sTileHeight = sHeight() / yTiles; int subTileWidth = sTileWidth / sampleSize; int subTileHeight = sTileHeight / sampleSize; + while (subTileWidth + xTiles + 1 > maxTileDimensions.x || (subTileWidth > getWidth() * 1.25 && sampleSize < fullImageSampleSize)) { xTiles += 1; sTileWidth = sWidth() / xTiles; subTileWidth = sTileWidth / sampleSize; } + while (subTileHeight + yTiles + 1 > maxTileDimensions.y || (subTileHeight > getHeight() * 1.25 && sampleSize < fullImageSampleSize)) { yTiles += 1; sTileHeight = sHeight() / yTiles; subTileHeight = sTileHeight / sampleSize; } + List tileGrid = new ArrayList<>(xTiles * yTiles); + for (int x = 0; x < xTiles; x++) { for (int y = 0; y < yTiles; y++) { Tile tile = new Tile(); @@ -1920,7 +1896,9 @@ private void initialiseTileMap(Point maxTileDimensions) { tileGrid.add(tile); } } + tileMap.put(sampleSize, tileGrid); + if (sampleSize == 1) { break; } else { @@ -2007,40 +1985,39 @@ protected void onPostExecute(int[] xyo) { } /** Called by worker task when decoder is ready and image size and EXIF orientation is known. */ - private synchronized void onTilesInited( - ImageRegionDecoder decoder, int sWidth, int sHeight, int sOrientation) { - debug("onTilesInited sWidth=%d, sHeight=%d, sOrientation=%d", sWidth, sHeight, orientation); + private synchronized void onTilesInited(ImageRegionDecoder decoder, int sWidth, int sHeight, int sOrientation) { + debug("onTilesInited sWidth=%d, sHeight=%d, sOrientation=%d", sWidth, sHeight, sOrientation); + // If actual dimensions don't match the declared size, reset everything. - if (this.sWidth > 0 - && this.sHeight > 0 - && (this.sWidth != sWidth || this.sHeight != sHeight)) { + if (this.sWidth > 0 && this.sHeight > 0 && (this.sWidth != sWidth || this.sHeight != sHeight)) { reset(false); + if (bitmap != null) { if (!bitmapIsCached) { bitmap.recycle(); } + bitmap = null; + if (onImageEventListener != null && bitmapIsCached) { onImageEventListener.onPreviewReleased(); } + bitmapIsPreview = false; bitmapIsCached = false; } } + this.decoder = decoder; this.sWidth = sWidth; this.sHeight = sHeight; this.sOrientation = sOrientation; checkReady(); - if (!checkImageLoaded() - && maxTileWidth > 0 - && maxTileWidth != TILE_SIZE_AUTO - && maxTileHeight > 0 - && maxTileHeight != TILE_SIZE_AUTO - && getWidth() > 0 - && getHeight() > 0) { + + if (!checkImageLoaded() && maxTileWidth > 0 && maxTileWidth != TILE_SIZE_AUTO && maxTileHeight > 0 && maxTileHeight != TILE_SIZE_AUTO && getWidth() > 0 && getHeight() > 0) { initialiseBaseLayer(new Point(maxTileWidth, maxTileHeight)); } + invalidate(); requestLayout(); animate().setInterpolator(new FastOutSlowInInterpolator()).alpha(1); @@ -2066,14 +2043,8 @@ protected Bitmap doInBackground(Void... params) { SubsamplingScaleImageView view = viewRef.get(); ImageRegionDecoder decoder = decoderRef.get(); Tile tile = tileRef.get(); - if (decoder != null - && tile != null - && view != null - && decoder.isReady() - && tile.visible) { - view.debug( - "TileLoadTask.doInBackground, tile.sRect=%s, tile.sampleSize=%d", - tile.sRect, tile.sampleSize); + if (decoder != null && tile != null && view != null && decoder.isReady() && tile.visible) { + view.debug("TileLoadTask.doInBackground, tile.sRect=%s, tile.sampleSize=%d", tile.sRect, tile.sampleSize); view.decoderLock.readLock().lock(); try { if (decoder.isReady()) { @@ -2106,6 +2077,7 @@ protected Bitmap doInBackground(Void... params) { protected void onPostExecute(Bitmap bitmap) { final SubsamplingScaleImageView subsamplingScaleImageView = viewRef.get(); final Tile tile = tileRef.get(); + if (subsamplingScaleImageView != null && tile != null) { if (bitmap != null) { tile.bitmap = bitmap; @@ -2124,6 +2096,7 @@ private synchronized void onTileLoaded() { debug("onTileLoaded"); checkReady(); checkImageLoaded(); + if (isBaseLayerReady() && bitmap != null) { if (!bitmapIsCached) { bitmap.recycle(); @@ -2135,6 +2108,7 @@ private synchronized void onTileLoaded() { bitmapIsPreview = false; bitmapIsCached = false; } + invalidate(); animate().setInterpolator(new FastOutSlowInInterpolator()).alpha(1); } @@ -2149,12 +2123,7 @@ private static class BitmapLoadTask extends AsyncTask { private Bitmap bitmap; private Exception exception; - BitmapLoadTask( - SubsamplingScaleImageView view, - Context context, - DecoderFactory decoderFactory, - Uri source, - boolean preview) { + BitmapLoadTask(SubsamplingScaleImageView view, Context context, DecoderFactory decoderFactory, Uri source, boolean preview) { this.viewRef = new WeakReference<>(view); this.contextRef = new WeakReference<>(context); this.decoderFactoryRef = @@ -2242,12 +2211,12 @@ private synchronized void onPreviewLoaded(Bitmap previewBitmap) { private synchronized void onImageLoaded( Bitmap bitmap, int sOrientation, boolean bitmapIsCached) { debug("onImageLoaded"); + // If actual dimensions don't match the declared size, reset everything. - if (this.sWidth > 0 - && this.sHeight > 0 - && (this.sWidth != bitmap.getWidth() || this.sHeight != bitmap.getHeight())) { + if (this.sWidth > 0 && this.sHeight > 0 && (this.sWidth != bitmap.getWidth() || this.sHeight != bitmap.getHeight())) { reset(false); } + if (this.bitmap != null && !this.bitmapIsCached) { this.bitmap.recycle(); } @@ -2264,10 +2233,12 @@ private synchronized void onImageLoaded( this.sOrientation = sOrientation; boolean ready = checkReady(); boolean imageLoaded = checkImageLoaded(); + if (ready || imageLoaded) { invalidate(); requestLayout(); } + animate().setInterpolator(new FastOutSlowInInterpolator()).alpha(1); } @@ -2278,16 +2249,17 @@ private synchronized void onImageLoaded( @AnyThread private int getExifOrientation(Context context, String sourceUri) { int exifOrientation = ORIENTATION_0; + if (sourceUri.startsWith(ContentResolver.SCHEME_CONTENT)) { Cursor cursor = null; try { String[] columns = {MediaStore.Images.Media.ORIENTATION}; - cursor = - context.getContentResolver() - .query(Uri.parse(sourceUri), columns, null, null, null); + cursor = context.getContentResolver().query(Uri.parse(sourceUri), columns, null, null, null); + if (cursor != null) { if (cursor.moveToFirst()) { int orientation = cursor.getInt(0); + if (VALID_ORIENTATIONS.contains(orientation) && orientation != ORIENTATION_USE_EXIF) { exifOrientation = orientation; @@ -2306,14 +2278,9 @@ private int getExifOrientation(Context context, String sourceUri) { } else if (sourceUri.startsWith(ImageSource.FILE_SCHEME) && !sourceUri.startsWith(ImageSource.ASSET_SCHEME)) { try { - ExifInterface exifInterface = - new ExifInterface( - sourceUri.substring(ImageSource.FILE_SCHEME.length() - 1)); - int orientationAttr = - exifInterface.getAttributeInt( - ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL); - if (orientationAttr == ExifInterface.ORIENTATION_NORMAL - || orientationAttr == ExifInterface.ORIENTATION_UNDEFINED) { + ExifInterface exifInterface = new ExifInterface(sourceUri.substring(ImageSource.FILE_SCHEME.length() - 1)); + int orientationAttr = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL); + if (orientationAttr == ExifInterface.ORIENTATION_NORMAL || orientationAttr == ExifInterface.ORIENTATION_UNDEFINED) { exifOrientation = ORIENTATION_0; } else if (orientationAttr == ExifInterface.ORIENTATION_ROTATE_90) { exifOrientation = ORIENTATION_90; @@ -2336,7 +2303,6 @@ private void execute(AsyncTask asyncTask) { } private static class Tile { - private Rect sRect; private int sampleSize; private Bitmap bitmap; @@ -2415,15 +2381,14 @@ public void setMaxTileSize(int maxPixelsX, int maxPixelsY) { */ @NonNull private Point getMaxBitmapDimensions(Canvas canvas) { - return new Point( - Math.min(canvas.getMaximumBitmapWidth(), maxTileWidth), - Math.min(canvas.getMaximumBitmapHeight(), maxTileHeight)); + return new Point(Math.min(canvas.getMaximumBitmapWidth(), maxTileWidth), Math.min(canvas.getMaximumBitmapHeight(), maxTileHeight)); } /** Get source width taking rotation into account. */ @SuppressWarnings("SuspiciousNameCombination") private int sWidth() { int rotation = getRequiredRotation(); + if (rotation == 90 || rotation == 270) { return sHeight; } else { @@ -2435,6 +2400,7 @@ private int sWidth() { @SuppressWarnings("SuspiciousNameCombination") private int sHeight() { int rotation = getRequiredRotation(); + if (rotation == 90 || rotation == 270) { return sWidth; } else { @@ -2502,6 +2468,7 @@ private float viewToSourceX(float vx) { if (vTranslate == null) { return Float.NaN; } + return (vx - vTranslate.x) / scale; } @@ -2510,6 +2477,7 @@ private float viewToSourceY(float vy) { if (vTranslate == null) { return Float.NaN; } + return (vy - vTranslate.y) / scale; } @@ -2530,17 +2498,9 @@ public void viewToFileRect(Rect vRect, Rect fRect) { if (vTranslate == null || !readySent) { return; } - fRect.set( - (int) viewToSourceX(vRect.left), - (int) viewToSourceY(vRect.top), - (int) viewToSourceX(vRect.right), - (int) viewToSourceY(vRect.bottom)); + fRect.set((int) viewToSourceX(vRect.left), (int) viewToSourceY(vRect.top), (int) viewToSourceX(vRect.right), (int) viewToSourceY(vRect.bottom)); fileSRect(fRect, fRect); - fRect.set( - Math.max(0, fRect.left), - Math.max(0, fRect.top), - Math.min(sWidth, fRect.right), - Math.min(sHeight, fRect.bottom)); + fRect.set(Math.max(0, fRect.left), Math.max(0, fRect.top), Math.min(sWidth, fRect.right), Math.min(sHeight, fRect.bottom)); if (sRegion != null) { fRect.offset(sRegion.left, sRegion.top); } @@ -2557,6 +2517,7 @@ public void visibleFileRect(Rect fRect) { if (vTranslate == null || !readySent) { return; } + fRect.set(0, 0, getWidth(), getHeight()); viewToFileRect(fRect, fRect); } @@ -2609,7 +2570,9 @@ public final PointF viewToSourceCoord(float vx, float vy, @NonNull PointF sTarge if (vTranslate == null) { return null; } + sTarget.set(viewToSourceX(vx), viewToSourceY(vy)); + return sTarget; } @@ -2618,6 +2581,7 @@ private float sourceToViewX(float sx) { if (vTranslate == null) { return Float.NaN; } + return (sx * scale) + vTranslate.x; } @@ -2626,6 +2590,7 @@ private float sourceToViewY(float sy) { if (vTranslate == null) { return Float.NaN; } + return (sy * scale) + vTranslate.y; } @@ -2678,17 +2643,14 @@ public final PointF sourceToViewCoord(float sx, float sy, @NonNull PointF vTarge if (vTranslate == null) { return null; } + vTarget.set(sourceToViewX(sx), sourceToViewY(sy)); return vTarget; } /** Convert source rect to screen rect, integer values. */ private void sourceToViewRect(@NonNull Rect sRect, @NonNull Rect vTarget) { - vTarget.set( - (int) sourceToViewX(sRect.left), - (int) sourceToViewY(sRect.top), - (int) sourceToViewX(sRect.right), - (int) sourceToViewY(sRect.bottom)); + vTarget.set((int) sourceToViewX(sRect.left), (int) sourceToViewY(sRect.top), (int) sourceToViewX(sRect.right), (int) sourceToViewY(sRect.bottom)); } /** @@ -2701,12 +2663,15 @@ private void sourceToViewRect(@NonNull Rect sRect, @NonNull Rect vTarget) { private PointF vTranslateForSCenter(float sCenterX, float sCenterY, float scale) { int vxCenter = getPaddingLeft() + (getWidth() - getPaddingRight() - getPaddingLeft()) / 2; int vyCenter = getPaddingTop() + (getHeight() - getPaddingBottom() - getPaddingTop()) / 2; + if (satTemp == null) { satTemp = new ScaleAndTranslate(0, new PointF(0, 0)); } + satTemp.scale = scale; satTemp.vTranslate.set(vxCenter - (sCenterX * scale), vyCenter - (sCenterY * scale)); fitToBounds(true, satTemp); + return satTemp.vTranslate; } @@ -2732,15 +2697,11 @@ private float minScale() { int vPadding = getPaddingBottom() + getPaddingTop(); int hPadding = getPaddingLeft() + getPaddingRight(); if (minimumScaleType == SCALE_TYPE_CENTER_CROP || minimumScaleType == SCALE_TYPE_START) { - return Math.max( - (getWidth() - hPadding) / (float) sWidth(), - (getHeight() - vPadding) / (float) sHeight()); + return Math.max((getWidth() - hPadding) / (float) sWidth(), (getHeight() - vPadding) / (float) sHeight()); } else if (minimumScaleType == SCALE_TYPE_CUSTOM && minScale > 0) { return minScale; } else { - return Math.min( - (getWidth() - hPadding) / (float) sWidth(), - (getHeight() - vPadding) / (float) sHeight()); + return Math.min((getWidth() - hPadding) / (float) sWidth(), (getHeight() - vPadding) / (float) sHeight()); } } @@ -2748,6 +2709,7 @@ private float minScale() { float limitedScale(float targetScale) { targetScale = Math.max(minScale(), targetScale); targetScale = Math.min(maxScale, targetScale); + return targetScale; } @@ -2783,6 +2745,7 @@ private float ease(int type, long time, float from, float change, long duration) */ private float easeOutQuad(long time, float from, float change, long duration) { float progress = (float) time / (float) duration; + return -change * progress * (progress - 2) + from; } @@ -2798,6 +2761,7 @@ private float easeOutQuad(long time, float from, float change, long duration) { */ private float easeInOutQuad(long time, float from, float change, long duration) { float timeF = time / (duration / 2f); + if (timeF < 1) { return (change / 2f * timeF * timeF) + from; } else { @@ -2826,12 +2790,12 @@ private int px(int px) { * * @param regionDecoderClass The {@link ImageRegionDecoder} implementation to use. */ - public final void setRegionDecoderClass( - @NonNull Class regionDecoderClass) { + public final void setRegionDecoderClass(@NonNull Class regionDecoderClass) { // noinspection ConstantConditions if (regionDecoderClass == null) { throw new IllegalArgumentException("Decoder class cannot be set to null"); } + this.regionDecoderFactory = new CompatDecoderFactory<>(regionDecoderClass); } @@ -2843,12 +2807,12 @@ public final void setRegionDecoderClass( * @param regionDecoderFactory The {@link DecoderFactory} implementation that produces {@link * ImageRegionDecoder} instances. */ - public final void setRegionDecoderFactory( - @NonNull DecoderFactory regionDecoderFactory) { + public final void setRegionDecoderFactory(@NonNull DecoderFactory regionDecoderFactory) { // noinspection ConstantConditions if (regionDecoderFactory == null) { throw new IllegalArgumentException("Decoder factory cannot be set to null"); } + this.regionDecoderFactory = regionDecoderFactory; } @@ -2859,12 +2823,12 @@ public final void setRegionDecoderFactory( * * @param bitmapDecoderClass The {@link ImageDecoder} implementation to use. */ - public final void setBitmapDecoderClass( - @NonNull Class bitmapDecoderClass) { + public final void setBitmapDecoderClass(@NonNull Class bitmapDecoderClass) { // noinspection ConstantConditions if (bitmapDecoderClass == null) { throw new IllegalArgumentException("Decoder class cannot be set to null"); } + this.bitmapDecoderFactory = new CompatDecoderFactory<>(bitmapDecoderClass); } @@ -2876,12 +2840,12 @@ public final void setBitmapDecoderClass( * @param bitmapDecoderFactory The {@link DecoderFactory} implementation that produces {@link * ImageDecoder} instances. */ - public final void setBitmapDecoderFactory( - @NonNull DecoderFactory bitmapDecoderFactory) { + public final void setBitmapDecoderFactory(@NonNull DecoderFactory bitmapDecoderFactory) { // noinspection ConstantConditions if (bitmapDecoderFactory == null) { throw new IllegalArgumentException("Decoder factory cannot be set to null"); } + this.bitmapDecoderFactory = bitmapDecoderFactory; } @@ -2928,7 +2892,9 @@ public final void setPanLimit(int panLimit) { if (!VALID_PAN_LIMITS.contains(panLimit)) { throw new IllegalArgumentException("Invalid pan limit: " + panLimit); } + this.panLimit = panLimit; + if (isReady()) { fitToBounds(true); invalidate(); @@ -2945,7 +2911,9 @@ public final void setMinimumScaleType(int scaleType) { if (!VALID_SCALE_TYPES.contains(scaleType)) { throw new IllegalArgumentException("Invalid scale type: " + scaleType); } + this.minimumScaleType = scaleType; + if (isReady()) { fitToBounds(true); invalidate(); @@ -3032,6 +3000,7 @@ public void setMinimumTileDpi(int minimumTileDpi) { DisplayMetrics metrics = getResources().getDisplayMetrics(); float averageDpi = (metrics.xdpi + metrics.ydpi) / 2; this.minimumTileDpi = (int) Math.min(averageDpi, minimumTileDpi); + if (isReady()) { reset(false); invalidate(); @@ -3081,11 +3050,13 @@ public final void setScaleAndCenter(float scale, @Nullable PointF sCenter) { public final void resetScaleAndCenter() { this.anim = null; this.pendingScale = limitedScale(0); + if (isReady()) { this.sPendingCenter = new PointF(sWidth() / 2.0f, sHeight() / 2.0f); } else { this.sPendingCenter = new PointF(0, 0); } + invalidate(); } @@ -3234,9 +3205,11 @@ public final boolean isPanEnabled() { */ public final void setPanEnabled(boolean panEnabled) { this.panEnabled = panEnabled; + if (!panEnabled && vTranslate != null) { vTranslate.x = (getWidth() / 2.0f) - (scale * (sWidth() / 2.0f)); vTranslate.y = (getHeight() / 2.0f) - (scale * (sHeight() / 2.0f)); + if (isReady()) { refreshRequiredTiles(true); invalidate(); @@ -3257,6 +3230,7 @@ public final void setTileBackgroundColor(int tileBgColor) { tileBgPaint.setStyle(Style.FILL); tileBgPaint.setColor(tileBgColor); } + invalidate(); } @@ -3294,6 +3268,7 @@ public final void setDoubleTapZoomStyle(int doubleTapZoomStyle) { if (!VALID_ZOOM_STYLES.contains(doubleTapZoomStyle)) { throw new IllegalArgumentException("Invalid zoom style: " + doubleTapZoomStyle); } + this.doubleTapZoomStyle = doubleTapZoomStyle; } @@ -3327,6 +3302,7 @@ public void setExecutor(@NonNull Executor executor) { if (executor == null) { throw new NullPointerException("Executor must not be null"); } + this.executor = executor; } @@ -3393,6 +3369,7 @@ private void sendStateChanged(float oldScale, PointF oldVTranslate, int origin) if (onStateChangedListener != null && scale != oldScale) { onStateChangedListener.onScaleChanged(scale, origin); } + if (onStateChangedListener != null && !vTranslate.equals(oldVTranslate)) { onStateChangedListener.onCenterChanged(getCenter(), origin); } @@ -3413,6 +3390,7 @@ public AnimationBuilder animateCenter(PointF sCenter) { if (!isReady()) { return null; } + return new AnimationBuilder(this, sCenter); } @@ -3430,6 +3408,7 @@ public AnimationBuilder animateScale(float scale) { if (!isReady()) { return null; } + return new AnimationBuilder(this, scale); } @@ -3448,6 +3427,7 @@ public AnimationBuilder animateScaleAndCenter(float scale, PointF sCenter) { if (!isReady()) { return null; } + return new AnimationBuilder(this, scale, sCenter); } @@ -3459,7 +3439,6 @@ public AnimationBuilder animateScaleAndCenter(float scale, PointF sCenter) { */ @SuppressWarnings("EmptyMethod") public interface OnAnimationEventListener { - /** The animation has completed, having reached its endpoint. */ void onComplete(); @@ -3481,7 +3460,6 @@ public interface OnAnimationEventListener { * in any method. */ public static class DefaultOnAnimationEventListener implements OnAnimationEventListener { - @Override public void onComplete() {} @@ -3557,7 +3535,6 @@ public interface OnImageEventListener { * any method. */ public static class DefaultOnImageEventListener implements OnImageEventListener { - @Override public void onReady() {} @@ -3617,4 +3594,4 @@ public void onCenterChanged(PointF newCenter, int origin) {} @Override public void onScaleChanged(float newScale, int origin) {} } -} +} \ No newline at end of file From 90f9e79c10512ce36bfa366e6a5b8e88cfee85be Mon Sep 17 00:00:00 2001 From: Nathan Grennan Date: Tue, 1 Apr 2025 00:40:15 -0700 Subject: [PATCH 16/99] Broke TouchEventUtil.java out of SubsamplingScaleImageView.java --- .../Views/SubsamplingScaleImageView.java | 339 ++---------------- .../redditslide/util/TouchEventUtil.java | 276 ++++++++++++++ 2 files changed, 311 insertions(+), 304 deletions(-) create mode 100644 app/src/main/java/me/edgan/redditslide/util/TouchEventUtil.java diff --git a/app/src/main/java/me/edgan/redditslide/Views/SubsamplingScaleImageView.java b/app/src/main/java/me/edgan/redditslide/Views/SubsamplingScaleImageView.java index 3a95c5a21..8a4431c20 100644 --- a/app/src/main/java/me/edgan/redditslide/Views/SubsamplingScaleImageView.java +++ b/app/src/main/java/me/edgan/redditslide/Views/SubsamplingScaleImageView.java @@ -44,6 +44,7 @@ import com.davemorrissey.labs.subscaleview.decoder.SkiaImageRegionDecoder; import me.edgan.redditslide.SettingValues; +import me.edgan.redditslide.util.TouchEventUtil; import java.lang.ref.WeakReference; import java.util.ArrayList; @@ -216,7 +217,7 @@ public class SubsamplingScaleImageView extends View { private int orientation = ORIENTATION_0; // Max scale allowed (prevent infinite zoom) - private float maxScale = 2F; + public float maxScale = 2F; // Min scale allowed (prevent infinite zoom) private float minScale = minScale(); @@ -239,12 +240,12 @@ public class SubsamplingScaleImageView extends View { private Executor executor = AsyncTask.THREAD_POOL_EXECUTOR; // Whether tiles should be loaded while gestures and animations are still in progress - private boolean eagerLoadingEnabled = true; + public boolean eagerLoadingEnabled = true; // Gesture detection settings - private boolean panEnabled = true; - private boolean zoomEnabled = true; - private boolean quickScaleEnabled = true; + public boolean panEnabled = true; + public boolean zoomEnabled = true; + public boolean quickScaleEnabled = true; // Double tap zoom behaviour private float doubleTapZoomScale = 1F; @@ -253,17 +254,17 @@ public class SubsamplingScaleImageView extends View { // Current scale and scale at start of zoom public float scale; - private float scaleStart; + public float scaleStart; // Screen coordinate of top-left corner of source image - private PointF vTranslate; - private PointF vTranslateStart; - private PointF vTranslateBefore; + public PointF vTranslate; + public PointF vTranslateStart; + public PointF vTranslateBefore; // Source coordinate to center on, used when new position is set externally before view is ready private Float pendingScale; private PointF sPendingCenter; - private PointF sRequestedCenter; + public PointF sRequestedCenter; // Source image dimensions and orientation - dimensions relate to the unrotated image private int sWidth; @@ -273,13 +274,13 @@ public class SubsamplingScaleImageView extends View { private Rect pRegion; // Is two-finger zooming in progress - private boolean isZooming; + public boolean isZooming; // Is one-finger panning in progress - private boolean isPanning; + public boolean isPanning; // Is quick-scale gesture in progress - private boolean isQuickScaling; + public boolean isQuickScaling; // Max touches used in current gesture - private int maxTouchCount; + public int maxTouchCount; // Fling detector private GestureDetector detector; @@ -302,19 +303,19 @@ public class SubsamplingScaleImageView extends View { : Bitmap.Config.RGB_565); // Debug values - private PointF vCenterStart; - private float vDistStart; + public PointF vCenterStart; + public float vDistStart; // Current quickscale state - private final float quickScaleThreshold; - private float quickScaleLastDistance; - private boolean quickScaleMoved; - private PointF quickScaleVLastPoint; - private PointF quickScaleSCenter; - private PointF quickScaleVStart; + public final float quickScaleThreshold; + public float quickScaleLastDistance; + public boolean quickScaleMoved; + public PointF quickScaleVLastPoint; + public PointF quickScaleSCenter; + public PointF quickScaleVStart; // Scale and center animation tracking - Anim anim; + public Anim anim; // Public because it's modified directly // Whether a ready notification has been sent to subclasses private boolean readySent; @@ -331,8 +332,8 @@ public class SubsamplingScaleImageView extends View { private OnLongClickListener onLongClickListener; // Long click handler - private final Handler handler; - private static final int MESSAGE_LONG_CLICK = 1; + public final Handler handler; + public static final int MESSAGE_LONG_CLICK = 1; // Make public static final // Paint objects created once and reused for efficiency private Paint bitmapPaint; @@ -349,7 +350,7 @@ public class SubsamplingScaleImageView extends View { private final float[] dstArray = new float[8]; // The logical density of the display - private final float density; + public final float density; // A global preference for bitmap format, available to decoder classes that respect it private static Bitmap.Config preferredBitmapConfig; @@ -861,283 +862,13 @@ public boolean onTouchEvent(@NonNull MotionEvent event) { float scaleBefore = scale; vTranslateBefore.set(vTranslate); - boolean handled = onTouchEventInternal(event); + boolean handled = TouchEventUtil.handleTouchEventInternal(this, event); sendStateChanged(scaleBefore, vTranslateBefore, ORIGIN_TOUCH); return handled || super.onTouchEvent(event); } - @SuppressWarnings("deprecation") - private boolean onTouchEventInternal(@NonNull MotionEvent event) { - int touchCount = event.getPointerCount(); - switch (event.getAction()) { - case MotionEvent.ACTION_DOWN: - case MotionEvent.ACTION_POINTER_1_DOWN: - case MotionEvent.ACTION_POINTER_2_DOWN: - anim = null; - requestDisallowInterceptTouchEvent(true); - maxTouchCount = Math.max(maxTouchCount, touchCount); - - if (touchCount >= 2) { - if (zoomEnabled) { - // Start pinch to zoom. Calculate distance between touch points and center - // point of the pinch. - float distance = - distance( - event.getX(0), event.getX(1), event.getY(0), event.getY(1)); - scaleStart = scale; - vDistStart = distance; - vTranslateStart.set(vTranslate.x, vTranslate.y); - vCenterStart.set( - (event.getX(0) + event.getX(1)) / 2, - (event.getY(0) + event.getY(1)) / 2); - } else { - // Abort all gestures on second touch - maxTouchCount = 0; - } - // Cancel long click timer - handler.removeMessages(MESSAGE_LONG_CLICK); - } else if (!isQuickScaling) { - // Start one-finger pan - vTranslateStart.set(vTranslate.x, vTranslate.y); - vCenterStart.set(event.getX(), event.getY()); - - // Start long click timer - handler.sendEmptyMessageDelayed(MESSAGE_LONG_CLICK, 600); - } - - return true; - case MotionEvent.ACTION_MOVE: - boolean consumed = false; - - if (maxTouchCount > 0) { - if (touchCount >= 2) { - // Calculate new distance between touch points, to scale and pan relative to - // start values. - float vDistEnd = - distance( - event.getX(0), event.getX(1), event.getY(0), event.getY(1)); - float vCenterEndX = (event.getX(0) + event.getX(1)) / 2; - float vCenterEndY = (event.getY(0) + event.getY(1)) / 2; - - if (zoomEnabled - && (distance( - vCenterStart.x, - vCenterEndX, - vCenterStart.y, - vCenterEndY) - > 5 - || Math.abs(vDistEnd - vDistStart) > 5 - || isPanning)) { - isZooming = true; - isPanning = true; - consumed = true; - - double previousScale = scale; - scale = Math.min(maxScale, (vDistEnd / vDistStart) * scaleStart); - - if (scale <= minScale()) { - // Minimum scale reached so don't pan. Adjust start settings so any - // expand will zoom in. - vDistStart = vDistEnd; - scaleStart = minScale(); - vCenterStart.set(vCenterEndX, vCenterEndY); - vTranslateStart.set(vTranslate); - } else if (panEnabled) { - // Translate to place the source image coordinate that was at the - // center of the pinch at the start - // at the center of the pinch now, to give simultaneous pan + zoom. - float vLeftStart = vCenterStart.x - vTranslateStart.x; - float vTopStart = vCenterStart.y - vTranslateStart.y; - float vLeftNow = vLeftStart * (scale / scaleStart); - float vTopNow = vTopStart * (scale / scaleStart); - vTranslate.x = vCenterEndX - vLeftNow; - vTranslate.y = vCenterEndY - vTopNow; - if ((previousScale * sHeight() < getHeight() - && scale * sHeight() >= getHeight()) - || (previousScale * sWidth() < getWidth() - && scale * sWidth() >= getWidth())) { - fitToBounds(true); - vCenterStart.set(vCenterEndX, vCenterEndY); - vTranslateStart.set(vTranslate); - scaleStart = scale; - vDistStart = vDistEnd; - } - } else if (sRequestedCenter != null) { - // With a center specified from code, zoom around that point. - vTranslate.x = (getWidth() / 2.0f) - (scale * sRequestedCenter.x); - vTranslate.y = (getHeight() / 2.0f) - (scale * sRequestedCenter.y); - } else { - // With no requested center, scale around the image center. - vTranslate.x = (getWidth() / 2.0f) - (scale * (sWidth() / 2.0f)); - vTranslate.y = (getHeight() / 2.0f) - (scale * (sHeight() / 2.0f)); - } - - fitToBounds(true); - refreshRequiredTiles(eagerLoadingEnabled); - } - } else if (isQuickScaling) { - // One finger zoom - // Stole Google's Magical Formula™ to make sure it feels the exact same - float dist = - Math.abs(quickScaleVStart.y - event.getY()) * 2 - + quickScaleThreshold; - - if (quickScaleLastDistance == -1f) { - quickScaleLastDistance = dist; - } - boolean isUpwards = event.getY() > quickScaleVLastPoint.y; - quickScaleVLastPoint.set(0, event.getY()); - - float spanDiff = Math.abs(1 - (dist / quickScaleLastDistance)) * 0.5f; - - if (spanDiff > 0.03f || quickScaleMoved) { - quickScaleMoved = true; - - float multiplier = 1; - if (quickScaleLastDistance > 0) { - multiplier = isUpwards ? (1 + spanDiff) : (1 - spanDiff); - } - - double previousScale = scale; - scale = Math.max(minScale(), Math.min(maxScale, scale * multiplier)); - - if (panEnabled) { - float vLeftStart = vCenterStart.x - vTranslateStart.x; - float vTopStart = vCenterStart.y - vTranslateStart.y; - float vLeftNow = vLeftStart * (scale / scaleStart); - float vTopNow = vTopStart * (scale / scaleStart); - vTranslate.x = vCenterStart.x - vLeftNow; - vTranslate.y = vCenterStart.y - vTopNow; - if ((previousScale * sHeight() < getHeight() - && scale * sHeight() >= getHeight()) - || (previousScale * sWidth() < getWidth() - && scale * sWidth() >= getWidth())) { - fitToBounds(true); - vCenterStart.set(sourceToViewCoord(quickScaleSCenter)); - vTranslateStart.set(vTranslate); - scaleStart = scale; - dist = 0; - } - } else if (sRequestedCenter != null) { - // With a center specified from code, zoom around that point. - vTranslate.x = (getWidth() / 2.0f) - (scale * sRequestedCenter.x); - vTranslate.y = (getHeight() / 2.0f) - (scale * sRequestedCenter.y); - } else { - // With no requested center, scale around the image center. - vTranslate.x = (getWidth() / 2.0f) - (scale * (sWidth() / 2.0f)); - vTranslate.y = (getHeight() / 2.0f) - (scale * (sHeight() / 2.0f)); - } - } - - quickScaleLastDistance = dist; - - fitToBounds(true); - refreshRequiredTiles(eagerLoadingEnabled); - - consumed = true; - } else if (!isZooming) { - // One finger pan - translate the image. We do this calculation even with - // pan disabled so click - // and long click behaviour is preserved. - float dx = Math.abs(event.getX() - vCenterStart.x); - float dy = Math.abs(event.getY() - vCenterStart.y); - - // On the Samsung S6 long click event does not work, because the dx > 5 - // usually true - float offset = density * 5; - if (dx > offset || dy > offset || isPanning) { - consumed = true; - vTranslate.x = vTranslateStart.x + (event.getX() - vCenterStart.x); - vTranslate.y = vTranslateStart.y + (event.getY() - vCenterStart.y); - - float lastX = vTranslate.x; - float lastY = vTranslate.y; - fitToBounds(true); - boolean atXEdge = lastX != vTranslate.x; - boolean atYEdge = lastY != vTranslate.y; - boolean edgeXSwipe = atXEdge && dx > dy && !isPanning; - boolean edgeYSwipe = atYEdge && dy > dx && !isPanning; - boolean yPan = lastY == vTranslate.y && dy > offset * 3; - if (!edgeXSwipe - && !edgeYSwipe - && (!atXEdge || !atYEdge || yPan || isPanning)) { - isPanning = true; - } else if (dx > offset || dy > offset) { - // Haven't panned the image, and we're at the left or right edge. - // Switch to page swipe. - maxTouchCount = 0; - handler.removeMessages(MESSAGE_LONG_CLICK); - requestDisallowInterceptTouchEvent(false); - } - - if (!panEnabled) { - vTranslate.x = vTranslateStart.x; - vTranslate.y = vTranslateStart.y; - requestDisallowInterceptTouchEvent(false); - } - - refreshRequiredTiles(eagerLoadingEnabled); - } - } - } - - if (consumed) { - handler.removeMessages(MESSAGE_LONG_CLICK); - invalidate(); - return true; - } - - break; - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_POINTER_UP: - case MotionEvent.ACTION_POINTER_2_UP: - handler.removeMessages(MESSAGE_LONG_CLICK); - - if (isQuickScaling) { - isQuickScaling = false; - if (!quickScaleMoved) { - doubleTapZoom(quickScaleSCenter, vCenterStart); - } - } - - if (maxTouchCount > 0 && (isZooming || isPanning)) { - if (isZooming && touchCount == 2) { - // Convert from zoom to pan with remaining touch - isPanning = true; - vTranslateStart.set(vTranslate.x, vTranslate.y); - if (event.getActionIndex() == 1) { - vCenterStart.set(event.getX(0), event.getY(0)); - } else { - vCenterStart.set(event.getX(1), event.getY(1)); - } - } - if (touchCount < 3) { - // End zooming when only one touch point - isZooming = false; - } - if (touchCount < 2) { - // End panning when no touch points - isPanning = false; - maxTouchCount = 0; - } - // Trigger load of tiles now required - refreshRequiredTiles(true); - return true; - } - - if (touchCount == 1) { - isZooming = false; - isPanning = false; - maxTouchCount = 0; - } - - return true; - } - - return false; - } - private void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { + public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { ViewParent parent = getParent(); if (parent != null) { parent.requestDisallowInterceptTouchEvent(disallowIntercept); @@ -1148,7 +879,7 @@ private void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { * Double tap zoom handler triggered from gesture detector or on touch, depending on whether * quick scale is enabled. */ - private void doubleTapZoom(PointF sCenter, PointF vFocus) { + public void doubleTapZoom(PointF sCenter, PointF vFocus) { if (!panEnabled) { if (sRequestedCenter != null) { // With a center specified from code, zoom around that point. @@ -1644,7 +1375,7 @@ private synchronized void initialiseBaseLayer(@NonNull Point maxTileDimensions) * @param load Whether to load the new tiles needed. Use false while scrolling/panning for * performance. */ - private void refreshRequiredTiles(boolean load) { + public void refreshRequiredTiles(boolean load) { if (decoder == null || tileMap == null) { return; } @@ -1827,7 +1558,7 @@ void fitToBounds(boolean center, ScaleAndTranslate sat) { * @param center Whether the image should be centered in the dimension it's too small to fill. * While animating this can be false to avoid changes in direction as bounds are reached. */ - void fitToBounds(boolean center) { + public void fitToBounds(boolean center) { boolean init = false; if (vTranslate == null) { @@ -2386,7 +2117,7 @@ private Point getMaxBitmapDimensions(Canvas canvas) { /** Get source width taking rotation into account. */ @SuppressWarnings("SuspiciousNameCombination") - private int sWidth() { + public int sWidth() { int rotation = getRequiredRotation(); if (rotation == 90 || rotation == 270) { @@ -2398,7 +2129,7 @@ private int sWidth() { /** Get source height taking rotation into account. */ @SuppressWarnings("SuspiciousNameCombination") - private int sHeight() { + public int sHeight() { int rotation = getRequiredRotation(); if (rotation == 90 || rotation == 270) { @@ -2693,7 +2424,7 @@ PointF limitedSCenter( } /** Returns the minimum allowed scale. */ - private float minScale() { + public float minScale() { int vPadding = getPaddingBottom() + getPaddingTop(); int hPadding = getPaddingLeft() + getPaddingRight(); if (minimumScaleType == SCALE_TYPE_CENTER_CROP || minimumScaleType == SCALE_TYPE_START) { diff --git a/app/src/main/java/me/edgan/redditslide/util/TouchEventUtil.java b/app/src/main/java/me/edgan/redditslide/util/TouchEventUtil.java new file mode 100644 index 000000000..46a78ca92 --- /dev/null +++ b/app/src/main/java/me/edgan/redditslide/util/TouchEventUtil.java @@ -0,0 +1,276 @@ +package me.edgan.redditslide.util; + +// Removed unused Handler import, will rely on view.handler +import android.view.MotionEvent; +import androidx.annotation.NonNull; + +import me.edgan.redditslide.Views.SubsamplingScaleImageView; + +/** + * Utility class for handling touch events for SubsamplingScaleImageView. + */ +public class TouchEventUtil { + + // Moved from SubsamplingScaleImageView + /** Pythagoras distance between two points. */ + private static float distance(float x0, float x1, float y0, float y1) { + float x = x0 - x1; + float y = y0 - y1; + return (float) Math.sqrt(x * x + y * y); + } + + // Copied and modified from SubsamplingScaleImageView + // Visibility of accessed members in SubsamplingScaleImageView might need adjustment (private -> package-private/protected) + @SuppressWarnings("deprecation") + public static boolean handleTouchEventInternal(@NonNull SubsamplingScaleImageView view, @NonNull MotionEvent event) { + int touchCount = event.getPointerCount(); + switch (event.getAction()) { + case MotionEvent.ACTION_DOWN: + case MotionEvent.ACTION_POINTER_1_DOWN: + case MotionEvent.ACTION_POINTER_2_DOWN: + view.anim = null; + view.requestDisallowInterceptTouchEvent(true); // Assuming accessible + view.maxTouchCount = Math.max(view.maxTouchCount, touchCount); + + if (touchCount >= 2) { + if (view.zoomEnabled) { + // Start pinch to zoom. Calculate distance between touch points and center point of the pinch. + float distance = distance(event.getX(0), event.getX(1), event.getY(0), event.getY(1)); + view.scaleStart = view.scale; + view.vDistStart = distance; + view.vTranslateStart.set(view.vTranslate.x, view.vTranslate.y); + view.vCenterStart.set((event.getX(0) + event.getX(1)) / 2, (event.getY(0) + event.getY(1)) / 2); + } else { + // Abort all gestures on second touch + view.maxTouchCount = 0; + } + // Cancel long click timer + view.handler.removeMessages(SubsamplingScaleImageView.MESSAGE_LONG_CLICK); + } else if (!view.isQuickScaling) { + // Start one-finger pan + view.vTranslateStart.set(view.vTranslate.x, view.vTranslate.y); + view.vCenterStart.set(event.getX(), event.getY()); + + // Start long click timer + view.handler.sendEmptyMessageDelayed(SubsamplingScaleImageView.MESSAGE_LONG_CLICK, 600); + } + + return true; + case MotionEvent.ACTION_MOVE: + boolean consumed = false; + + if (view.maxTouchCount > 0) { + if (touchCount >= 2) { + // Calculate new distance between touch points, to scale and pan relative to start values. + float vDistEnd = distance(event.getX(0), event.getX(1), event.getY(0), event.getY(1)); + float vCenterEndX = (event.getX(0) + event.getX(1)) / 2; + float vCenterEndY = (event.getY(0) + event.getY(1)) / 2; + + if (view.zoomEnabled && ( + distance(view.vCenterStart.x, vCenterEndX, view.vCenterStart.y, vCenterEndY) > 5 + || Math.abs(vDistEnd - view.vDistStart) > 5 + || view.isPanning)) { + view.isZooming = true; + view.isPanning = true; + consumed = true; + + double previousScale = view.scale; + view.scale = Math.min(view.maxScale, (vDistEnd / view.vDistStart) * view.scaleStart); + + if (view.scale <= view.minScale()) { + // Minimum scale reached so don't pan. Adjust start settings so any expand will zoom in. + view.vDistStart = vDistEnd; + view.scaleStart = view.minScale(); + view.vCenterStart.set(vCenterEndX, vCenterEndY); + view.vTranslateStart.set(view.vTranslate); + } else if (view.panEnabled) { + // Translate to place the source image coordinate that was at the center of the pinch at the start at the center of the pinch now, to give simultaneous pan + zoom. + float vLeftStart = view.vCenterStart.x - view.vTranslateStart.x; + float vTopStart = view.vCenterStart.y - view.vTranslateStart.y; + float vLeftNow = vLeftStart * (view.scale / view.scaleStart); + float vTopNow = vTopStart * (view.scale / view.scaleStart); + view.vTranslate.x = vCenterEndX - vLeftNow; + view.vTranslate.y = vCenterEndY - vTopNow; + if ((previousScale * view.sHeight() < view.getHeight() + && view.scale * view.sHeight() >= view.getHeight()) + || (previousScale * view.sWidth() < view.getWidth() + && view.scale * view.sWidth() >= view.getWidth())) { + view.fitToBounds(true); + view.vCenterStart.set(vCenterEndX, vCenterEndY); + view.vTranslateStart.set(view.vTranslate); + view.scaleStart = view.scale; + view.vDistStart = vDistEnd; + } + } else if (view.sRequestedCenter != null) { + // With a center specified from code, zoom around that point. + view.vTranslate.x = (view.getWidth() / 2.0f) - (view.scale * view.sRequestedCenter.x); + view.vTranslate.y = (view.getHeight() / 2.0f) - (view.scale * view.sRequestedCenter.y); + } else { + // With no requested center, scale around the image center. + view.vTranslate.x = (view.getWidth() / 2.0f) - (view.scale * (view.sWidth() / 2.0f)); + view.vTranslate.y = (view.getHeight() / 2.0f) - (view.scale * (view.sHeight() / 2.0f)); + } + + view.fitToBounds(true); + view.refreshRequiredTiles(view.eagerLoadingEnabled); + } + } else if (view.isQuickScaling) { + // One finger zoom + // Stole Google's Magical Formula™ to make sure it feels the exact same + float dist = Math.abs(view.quickScaleVStart.y - event.getY()) * 2 + view.quickScaleThreshold; + + if (view.quickScaleLastDistance == -1f) { + view.quickScaleLastDistance = dist; + } + + boolean isUpwards = event.getY() > view.quickScaleVLastPoint.y; + view.quickScaleVLastPoint.set(0, event.getY()); + + float spanDiff = Math.abs(1 - (dist / view.quickScaleLastDistance)) * 0.5f; + + if (spanDiff > 0.03f || view.quickScaleMoved) { + view.quickScaleMoved = true; + + float multiplier = 1; + + if (view.quickScaleLastDistance > 0) { + multiplier = isUpwards ? (1 + spanDiff) : (1 - spanDiff); + } + + double previousScale = view.scale; + view.scale = Math.max(view.minScale(), Math.min(view.maxScale, view.scale * multiplier)); + + if (view.panEnabled) { + float vLeftStart = view.vCenterStart.x - view.vTranslateStart.x; + float vTopStart = view.vCenterStart.y - view.vTranslateStart.y; + float vLeftNow = vLeftStart * (view.scale / view.scaleStart); + float vTopNow = vTopStart * (view.scale / view.scaleStart); + view.vTranslate.x = view.vCenterStart.x - vLeftNow; + view.vTranslate.y = view.vCenterStart.y - vTopNow; + + if ((previousScale * view.sHeight() < view.getHeight() + && view.scale * view.sHeight() >= view.getHeight()) + || (previousScale * view.sWidth() < view.getWidth() + && view.scale * view.sWidth() >= view.getWidth())) { + view.fitToBounds(true); + view.vCenterStart.set(view.sourceToViewCoord(view.quickScaleSCenter)); + view.vTranslateStart.set(view.vTranslate); + view.scaleStart = view.scale; + dist = 0; + } + } else if (view.sRequestedCenter != null) { + // With a center specified from code, zoom around that point. + view.vTranslate.x = (view.getWidth() / 2.0f) - (view.scale * view.sRequestedCenter.x); + view.vTranslate.y = (view.getHeight() / 2.0f) - (view.scale * view.sRequestedCenter.y); + } else { + // With no requested center, scale around the image center. + view.vTranslate.x = (view.getWidth() / 2.0f) - (view.scale * (view.sWidth() / 2.0f)); + view.vTranslate.y = (view.getHeight() / 2.0f) - (view.scale * (view.sHeight() / 2.0f)); + } + } + + view.quickScaleLastDistance = dist; + + view.fitToBounds(true); + view.refreshRequiredTiles(view.eagerLoadingEnabled); + + consumed = true; + } else if (!view.isZooming) { + // One finger pan - translate the image. We do this calculation even with pan disabled so click and long click behaviour is preserved. + float dx = Math.abs(event.getX() - view.vCenterStart.x); + float dy = Math.abs(event.getY() - view.vCenterStart.y); + + // On the Samsung S6 long click event does not work, because the dx > 5 usually true + float offset = view.density * 5; + + if (dx > offset || dy > offset || view.isPanning) { + consumed = true; + view.vTranslate.x = view.vTranslateStart.x + (event.getX() - view.vCenterStart.x); + view.vTranslate.y = view.vTranslateStart.y + (event.getY() - view.vCenterStart.y); + + float lastX = view.vTranslate.x; + float lastY = view.vTranslate.y; + view.fitToBounds(true); + boolean atXEdge = lastX != view.vTranslate.x; + boolean atYEdge = lastY != view.vTranslate.y; + boolean edgeXSwipe = atXEdge && dx > dy && !view.isPanning; + boolean edgeYSwipe = atYEdge && dy > dx && !view.isPanning; + boolean yPan = lastY == view.vTranslate.y && dy > offset * 3; + + if (!edgeXSwipe && !edgeYSwipe && (!atXEdge || !atYEdge || yPan || view.isPanning)) { + view.isPanning = true; + } else if (dx > offset || dy > offset) { + // Haven't panned the image, and we're at the left or right edge. + // Switch to page swipe. + view.maxTouchCount = 0; + view.handler.removeMessages(SubsamplingScaleImageView.MESSAGE_LONG_CLICK); + view.requestDisallowInterceptTouchEvent(false); + } + + if (!view.panEnabled) { + view.vTranslate.x = view.vTranslateStart.x; + view.vTranslate.y = view.vTranslateStart.y; + view.requestDisallowInterceptTouchEvent(false); + } + + view.refreshRequiredTiles(view.eagerLoadingEnabled); + } + } + } + + if (consumed) { + view.handler.removeMessages(SubsamplingScaleImageView.MESSAGE_LONG_CLICK); + view.invalidate(); + return true; + } + + break; + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_POINTER_UP: + case MotionEvent.ACTION_POINTER_2_UP: + view.handler.removeMessages(SubsamplingScaleImageView.MESSAGE_LONG_CLICK); + + if (view.isQuickScaling) { + view.isQuickScaling = false; + if (!view.quickScaleMoved) { + view.doubleTapZoom(view.quickScaleSCenter, view.vCenterStart); + } + } + + if (view.maxTouchCount > 0 && (view.isZooming || view.isPanning)) { + if (view.isZooming && touchCount == 2) { + // Convert from zoom to pan with remaining touch + view.isPanning = true; + view.vTranslateStart.set(view.vTranslate.x, view.vTranslate.y); + if (event.getActionIndex() == 1) { + view.vCenterStart.set(event.getX(0), event.getY(0)); + } else { + view.vCenterStart.set(event.getX(1), event.getY(1)); + } + } + if (touchCount < 3) { + // End zooming when only one touch point + view.isZooming = false; + } + if (touchCount < 2) { + // End panning when no touch points + view.isPanning = false; + view.maxTouchCount = 0; + } + // Trigger load of tiles now required + view.refreshRequiredTiles(true); + return true; + } + + if (touchCount == 1) { + view.isZooming = false; + view.isPanning = false; + view.maxTouchCount = 0; + } + + return true; + } + + return false; + } +} \ No newline at end of file From e8554e0c49577b63d3c0f631e593385d22fa15c5 Mon Sep 17 00:00:00 2001 From: Nathan Grennan Date: Tue, 1 Apr 2025 00:50:53 -0700 Subject: [PATCH 17/99] Clean up --- .../me/edgan/redditslide/Views/SubsamplingScaleImageView.java | 1 - .../main/java/me/edgan/redditslide/util/TouchEventUtil.java | 4 +--- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/app/src/main/java/me/edgan/redditslide/Views/SubsamplingScaleImageView.java b/app/src/main/java/me/edgan/redditslide/Views/SubsamplingScaleImageView.java index 8a4431c20..75bf50af9 100644 --- a/app/src/main/java/me/edgan/redditslide/Views/SubsamplingScaleImageView.java +++ b/app/src/main/java/me/edgan/redditslide/Views/SubsamplingScaleImageView.java @@ -867,7 +867,6 @@ public boolean onTouchEvent(@NonNull MotionEvent event) { return handled || super.onTouchEvent(event); } - public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { ViewParent parent = getParent(); if (parent != null) { diff --git a/app/src/main/java/me/edgan/redditslide/util/TouchEventUtil.java b/app/src/main/java/me/edgan/redditslide/util/TouchEventUtil.java index 46a78ca92..3f4c6fc36 100644 --- a/app/src/main/java/me/edgan/redditslide/util/TouchEventUtil.java +++ b/app/src/main/java/me/edgan/redditslide/util/TouchEventUtil.java @@ -1,6 +1,5 @@ package me.edgan.redditslide.util; -// Removed unused Handler import, will rely on view.handler import android.view.MotionEvent; import androidx.annotation.NonNull; @@ -20,7 +19,6 @@ private static float distance(float x0, float x1, float y0, float y1) { } // Copied and modified from SubsamplingScaleImageView - // Visibility of accessed members in SubsamplingScaleImageView might need adjustment (private -> package-private/protected) @SuppressWarnings("deprecation") public static boolean handleTouchEventInternal(@NonNull SubsamplingScaleImageView view, @NonNull MotionEvent event) { int touchCount = event.getPointerCount(); @@ -29,7 +27,7 @@ public static boolean handleTouchEventInternal(@NonNull SubsamplingScaleImageVie case MotionEvent.ACTION_POINTER_1_DOWN: case MotionEvent.ACTION_POINTER_2_DOWN: view.anim = null; - view.requestDisallowInterceptTouchEvent(true); // Assuming accessible + view.requestDisallowInterceptTouchEvent(true); view.maxTouchCount = Math.max(view.maxTouchCount, touchCount); if (touchCount >= 2) { From 3b5941f9b71b6134b9461f1e1e23194917c515e7 Mon Sep 17 00:00:00 2001 From: Nathan Grennan Date: Tue, 1 Apr 2025 02:04:53 -0700 Subject: [PATCH 18/99] Broke SubsamplingScaleImageViewDrawHelper.java out of SubsamplingScaleImageView.java --- .../redditslide/Views/AnimationBuilder.java | 4 +- .../Views/SubsamplingScaleImageView.java | 498 +++--------------- .../SubsamplingScaleImageViewDrawHelper.java | 398 ++++++++++++++ 3 files changed, 467 insertions(+), 433 deletions(-) create mode 100644 app/src/main/java/me/edgan/redditslide/util/SubsamplingScaleImageViewDrawHelper.java diff --git a/app/src/main/java/me/edgan/redditslide/Views/AnimationBuilder.java b/app/src/main/java/me/edgan/redditslide/Views/AnimationBuilder.java index 3fa01e3db..d0632b6a5 100644 --- a/app/src/main/java/me/edgan/redditslide/Views/AnimationBuilder.java +++ b/app/src/main/java/me/edgan/redditslide/Views/AnimationBuilder.java @@ -4,6 +4,8 @@ import android.util.Log; import androidx.annotation.NonNull; +import me.edgan.redditslide.util.SubsamplingScaleImageViewDrawHelper; + import java.util.Arrays; import java.util.List; @@ -166,7 +168,7 @@ public void start() { float vTranslateYEnd = vFocus.y - (targetScale * view.anim.sCenterStart.y); SubsamplingScaleImageView.ScaleAndTranslate satEnd = new SubsamplingScaleImageView.ScaleAndTranslate(targetScale, new PointF(vTranslateXEnd, vTranslateYEnd)); // Fit the end translation into bounds - view.fitToBounds(true, satEnd); + SubsamplingScaleImageViewDrawHelper.fitToBounds(view, true, satEnd); // Adjust the position of the focus point at end so image will be in bounds view.anim.vFocusEnd = new PointF(vFocus.x + (satEnd.vTranslate.x - vTranslateXEnd), vFocus.y + (satEnd.vTranslate.y - vTranslateYEnd)); } diff --git a/app/src/main/java/me/edgan/redditslide/Views/SubsamplingScaleImageView.java b/app/src/main/java/me/edgan/redditslide/Views/SubsamplingScaleImageView.java index 75bf50af9..76b58a1be 100644 --- a/app/src/main/java/me/edgan/redditslide/Views/SubsamplingScaleImageView.java +++ b/app/src/main/java/me/edgan/redditslide/Views/SubsamplingScaleImageView.java @@ -44,6 +44,7 @@ import com.davemorrissey.labs.subscaleview.decoder.SkiaImageRegionDecoder; import me.edgan.redditslide.SettingValues; +import me.edgan.redditslide.util.SubsamplingScaleImageViewDrawHelper; import me.edgan.redditslide.util.TouchEventUtil; import java.lang.ref.WeakReference; @@ -51,7 +52,6 @@ import java.util.Arrays; import java.util.LinkedHashMap; import java.util.List; -import java.util.Locale; import java.util.Map; import java.util.concurrent.Executor; import java.util.concurrent.locks.ReadWriteLock; @@ -193,25 +193,25 @@ public class SubsamplingScaleImageView extends View { public static final int ORIGIN_DOUBLE_TAP_ZOOM = 4; // Bitmap (preview or full image) - private Bitmap bitmap; + public Bitmap bitmap; // Whether the bitmap is a preview image - private boolean bitmapIsPreview; + public boolean bitmapIsPreview; // Specifies if a cache handler is also referencing the bitmap. Do not recycle if so. - private boolean bitmapIsCached; + public boolean bitmapIsCached; // Uri of full size image private Uri uri; // Sample size used to display the whole image when fully zoomed out - private int fullImageSampleSize; + public int fullImageSampleSize; // Map of zoom level to tile grid - private Map> tileMap; + public Map> tileMap; // Overlay tile boundaries and other info - private boolean debug; + public boolean debug; // Image orientation setting private int orientation = ORIENTATION_0; @@ -226,7 +226,7 @@ public class SubsamplingScaleImageView extends View { private int minimumTileDpi = -1; // Pan limiting style - private int panLimit = PAN_LIMIT_INSIDE; + public int panLimit = PAN_LIMIT_INSIDE; // Minimum scale type private int minimumScaleType = SCALE_TYPE_CENTER_INSIDE; @@ -267,9 +267,9 @@ public class SubsamplingScaleImageView extends View { public PointF sRequestedCenter; // Source image dimensions and orientation - dimensions relate to the unrotated image - private int sWidth; - private int sHeight; - private int sOrientation; + public int sWidth; + public int sHeight; + public int sOrientation; private Rect sRegion; private Rect pRegion; @@ -287,7 +287,7 @@ public class SubsamplingScaleImageView extends View { private GestureDetector singleDetector; // Tile and image decoding - private ImageRegionDecoder decoder; + public ImageRegionDecoder decoder; private final ReadWriteLock decoderLock = new ReentrantReadWriteLock(true); private DecoderFactory bitmapDecoderFactory = new CompatDecoderFactory( @@ -315,12 +315,12 @@ public class SubsamplingScaleImageView extends View { public PointF quickScaleVStart; // Scale and center animation tracking - public Anim anim; // Public because it's modified directly + public Anim anim; // Whether a ready notification has been sent to subclasses - private boolean readySent; + public boolean readySent; // Whether a base layer loaded notification has been sent to subclasses - private boolean imageLoadedSent; + public boolean imageLoadedSent; // Event listener private OnImageEventListener onImageEventListener; @@ -333,21 +333,21 @@ public class SubsamplingScaleImageView extends View { // Long click handler public final Handler handler; - public static final int MESSAGE_LONG_CLICK = 1; // Make public static final + public static final int MESSAGE_LONG_CLICK = 1; // Paint objects created once and reused for efficiency - private Paint bitmapPaint; - private Paint debugPaint; - private Paint debugTextPaint; - private Paint debugLinePaint; - private Paint tileBgPaint; + public Paint bitmapPaint; + public Paint debugPaint; + public Paint debugTextPaint; + public Paint debugLinePaint; + public Paint tileBgPaint; // Volatile fields used to reduce object creation ScaleAndTranslate satTemp; - private Matrix matrix; - private RectF sRect; - private final float[] srcArray = new float[8]; - private final float[] dstArray = new float[8]; + public Matrix matrix; + public RectF sRect; + public float[] srcArray = new float[8]; + public float[] dstArray = new float[8]; // The logical density of the display public final float density; @@ -922,328 +922,12 @@ public void doubleTapZoom(PointF sCenter, PointF vFocus) { @Override protected void onDraw(Canvas canvas) { super.onDraw(canvas); - createPaints(); - - // If image or view dimensions are not known yet, abort. - if (sWidth == 0 || sHeight == 0 || getWidth() == 0 || getHeight() == 0) { - return; - } - - // When using tiles, on first render with no tile map ready, initialise it and kick off - // async base image loading. - if (tileMap == null && decoder != null) { - initialiseBaseLayer(getMaxBitmapDimensions(canvas)); - } - - // If image has been loaded or supplied as a bitmap, onDraw may be the first time the view - // has - // dimensions and therefore the first opportunity to set scale and translate. If this call - // returns - // false there is nothing to be drawn so return immediately. - if (!checkReady()) { - return; - } - - // Set scale and translate before draw. - preDraw(); - - // If animating scale, calculate current scale and center with easing equations - if (anim != null && anim.vFocusStart != null) { - // Store current values so we can send an event if they change - float scaleBefore = scale; - if (vTranslateBefore == null) { - vTranslateBefore = new PointF(0, 0); - } - vTranslateBefore.set(vTranslate); - - long scaleElapsed = System.currentTimeMillis() - anim.time; - boolean finished = scaleElapsed > anim.duration; - scaleElapsed = Math.min(scaleElapsed, anim.duration); - scale = - ease( - anim.easing, - scaleElapsed, - anim.scaleStart, - anim.scaleEnd - anim.scaleStart, - anim.duration); - - // Apply required animation to the focal point - float vFocusNowX = ease(anim.easing, scaleElapsed, anim.vFocusStart.x, anim.vFocusEnd.x - anim.vFocusStart.x, anim.duration); - float vFocusNowY = ease(anim.easing, scaleElapsed, anim.vFocusStart.y, anim.vFocusEnd.y - anim.vFocusStart.y, anim.duration); - - // Find out where the focal point is at this scale and adjust its position to follow the animation path - vTranslate.x -= sourceToViewX(anim.sCenterEnd.x) - vFocusNowX; - vTranslate.y -= sourceToViewY(anim.sCenterEnd.y) - vFocusNowY; - - // For translate anims, showing the image non-centered is never allowed, for scaling - // anims it is during the animation. - fitToBounds(finished || (anim.scaleStart == anim.scaleEnd)); - sendStateChanged(scaleBefore, vTranslateBefore, anim.origin); - refreshRequiredTiles(finished); - - if (finished) { - if (anim.listener != null) { - try { - anim.listener.onComplete(); - } catch (Exception e) { - Log.w(TAG, "Error thrown by animation listener", e); - } - } - anim = null; - } - - invalidate(); - } - - if (tileMap != null && isBaseLayerReady()) { - // Optimum sample size for current scale - int sampleSize = Math.min(fullImageSampleSize, calculateInSampleSize(scale)); - - // First check for missing tiles - if there are any we need the base layer underneath to - // avoid gaps - boolean hasMissingTiles = false; - - for (Map.Entry> tileMapEntry : tileMap.entrySet()) { - if (tileMapEntry.getKey() == sampleSize) { - for (Tile tile : tileMapEntry.getValue()) { - if (tile.visible && (tile.loading || tile.bitmap == null)) { - hasMissingTiles = true; - break; - } - } - } - } - - // Render all loaded tiles. LinkedHashMap used for bottom up rendering - lower res tiles - // underneath. - for (Map.Entry> tileMapEntry : tileMap.entrySet()) { - if (tileMapEntry.getKey() == sampleSize || hasMissingTiles) { - for (Tile tile : tileMapEntry.getValue()) { - sourceToViewRect(tile.sRect, tile.vRect); - if (!tile.loading && tile.bitmap != null) { - if (tileBgPaint != null) { - canvas.drawRect(tile.vRect, tileBgPaint); - } - if (matrix == null) { - matrix = new Matrix(); - } - matrix.reset(); - setMatrixArray( - srcArray, - 0, - 0, - tile.bitmap.getWidth(), - 0, - tile.bitmap.getWidth(), - tile.bitmap.getHeight(), - 0, - tile.bitmap.getHeight()); - if (getRequiredRotation() == ORIENTATION_0) { - setMatrixArray( - dstArray, - tile.vRect.left, - tile.vRect.top, - tile.vRect.right, - tile.vRect.top, - tile.vRect.right, - tile.vRect.bottom, - tile.vRect.left, - tile.vRect.bottom); - } else if (getRequiredRotation() == ORIENTATION_90) { - setMatrixArray( - dstArray, - tile.vRect.right, - tile.vRect.top, - tile.vRect.right, - tile.vRect.bottom, - tile.vRect.left, - tile.vRect.bottom, - tile.vRect.left, - tile.vRect.top); - } else if (getRequiredRotation() == ORIENTATION_180) { - setMatrixArray( - dstArray, - tile.vRect.right, - tile.vRect.bottom, - tile.vRect.left, - tile.vRect.bottom, - tile.vRect.left, - tile.vRect.top, - tile.vRect.right, - tile.vRect.top); - } else if (getRequiredRotation() == ORIENTATION_270) { - setMatrixArray( - dstArray, - tile.vRect.left, - tile.vRect.bottom, - tile.vRect.left, - tile.vRect.top, - tile.vRect.right, - tile.vRect.top, - tile.vRect.right, - tile.vRect.bottom); - } - matrix.setPolyToPoly(srcArray, 0, dstArray, 0, 4); - canvas.drawBitmap(tile.bitmap, matrix, bitmapPaint); - if (debug) { - canvas.drawRect(tile.vRect, debugLinePaint); - } - } else if (tile.loading && debug) { - canvas.drawText( - "LOADING", - tile.vRect.left + px(5), - tile.vRect.top + px(35), - debugTextPaint); - } - if (tile.visible && debug) { - canvas.drawText( - "ISS " - + tile.sampleSize - + " RECT " - + tile.sRect.top - + "," - + tile.sRect.left - + "," - + tile.sRect.bottom - + "," - + tile.sRect.right, - tile.vRect.left + px(5), - tile.vRect.top + px(15), - debugTextPaint); - } - } - } - } - } else if (bitmap != null && !bitmap.isRecycled()) { - - float xScale = scale, yScale = scale; - - if (bitmapIsPreview) { - xScale = scale * ((float) sWidth / bitmap.getWidth()); - yScale = scale * ((float) sHeight / bitmap.getHeight()); - } - - if (matrix == null) { - matrix = new Matrix(); - } - - matrix.reset(); - matrix.postScale(xScale, yScale); - matrix.postRotate(getRequiredRotation()); - matrix.postTranslate(vTranslate.x, vTranslate.y); - - if (getRequiredRotation() == ORIENTATION_180) { - matrix.postTranslate(scale * sWidth, scale * sHeight); - } else if (getRequiredRotation() == ORIENTATION_90) { - matrix.postTranslate(scale * sHeight, 0); - } else if (getRequiredRotation() == ORIENTATION_270) { - matrix.postTranslate(0, scale * sWidth); - } - - if (tileBgPaint != null) { - if (sRect == null) { - sRect = new RectF(); - } - sRect.set( - 0f, - 0f, - bitmapIsPreview ? bitmap.getWidth() : sWidth, - bitmapIsPreview ? bitmap.getHeight() : sHeight); - matrix.mapRect(sRect); - canvas.drawRect(sRect, tileBgPaint); - } - canvas.drawBitmap(bitmap, matrix, bitmapPaint); - } - - if (debug) { - canvas.drawText( - "Scale: " - + String.format(Locale.ENGLISH, "%.2f", scale) - + " (" - + String.format(Locale.ENGLISH, "%.2f", minScale()) - + " - " - + String.format(Locale.ENGLISH, "%.2f", maxScale) - + ")", - px(5), - px(15), - debugTextPaint); - canvas.drawText( - "Translate: " - + String.format(Locale.ENGLISH, "%.2f", vTranslate.x) - + ":" - + String.format(Locale.ENGLISH, "%.2f", vTranslate.y), - px(5), - px(30), - debugTextPaint); - PointF center = getCenter(); - // noinspection ConstantConditions - canvas.drawText( - "Source center: " - + String.format(Locale.ENGLISH, "%.2f", center.x) - + ":" - + String.format(Locale.ENGLISH, "%.2f", center.y), - px(5), - px(45), - debugTextPaint); - if (anim != null) { - PointF vCenterStart = sourceToViewCoord(anim.sCenterStart); - PointF vCenterEndRequested = sourceToViewCoord(anim.sCenterEndRequested); - PointF vCenterEnd = sourceToViewCoord(anim.sCenterEnd); - // noinspection ConstantConditions - canvas.drawCircle(vCenterStart.x, vCenterStart.y, px(10), debugLinePaint); - debugLinePaint.setColor(Color.RED); - // noinspection ConstantConditions - canvas.drawCircle( - vCenterEndRequested.x, vCenterEndRequested.y, px(20), debugLinePaint); - debugLinePaint.setColor(Color.BLUE); - // noinspection ConstantConditions - canvas.drawCircle(vCenterEnd.x, vCenterEnd.y, px(25), debugLinePaint); - debugLinePaint.setColor(Color.CYAN); - canvas.drawCircle(getWidth() / 2.0f, getHeight() / 2.0f, px(30), debugLinePaint); - } - if (vCenterStart != null) { - debugLinePaint.setColor(Color.RED); - canvas.drawCircle(vCenterStart.x, vCenterStart.y, px(20), debugLinePaint); - } - if (quickScaleSCenter != null) { - debugLinePaint.setColor(Color.BLUE); - canvas.drawCircle( - sourceToViewX(quickScaleSCenter.x), - sourceToViewY(quickScaleSCenter.y), - px(35), - debugLinePaint); - } - if (quickScaleVStart != null && isQuickScaling) { - debugLinePaint.setColor(Color.CYAN); - canvas.drawCircle(quickScaleVStart.x, quickScaleVStart.y, px(30), debugLinePaint); - } - debugLinePaint.setColor(Color.MAGENTA); - } - } - - /** Helper method for setting the values of a tile matrix array. */ - private void setMatrixArray( - float[] array, - float f0, - float f1, - float f2, - float f3, - float f4, - float f5, - float f6, - float f7) { - array[0] = f0; - array[1] = f1; - array[2] = f2; - array[3] = f3; - array[4] = f4; - array[5] = f5; - array[6] = f6; - array[7] = f7; + // Delegate drawing to the helper class + me.edgan.redditslide.util.SubsamplingScaleImageViewDrawHelper.drawContent(this, canvas); } /** Checks whether the base layer of tiles or full size bitmap is ready. */ - private boolean isBaseLayerReady() { + public boolean isBaseLayerReady() { if (bitmap != null && !bitmapIsPreview) { return true; } else if (tileMap != null) { @@ -1268,7 +952,7 @@ private boolean isBaseLayerReady() { * base layer tiles are loaded. First time, send ready event to listener. The next draw will * display an image. */ - private boolean checkReady() { + public boolean checkReady() { boolean ready = getWidth() > 0 && getHeight() > 0 && sWidth > 0 && sHeight > 0 && (bitmap != null || isBaseLayerReady()); if (!readySent && ready) { @@ -1288,7 +972,7 @@ private boolean checkReady() { * Check whether either the full size bitmap or base layer tiles are loaded. First time, send * image loaded event to listener. */ - private boolean checkImageLoaded() { + public boolean checkImageLoaded() { boolean imageLoaded = isBaseLayerReady(); if (!imageLoadedSent && imageLoaded) { @@ -1304,7 +988,7 @@ private boolean checkImageLoaded() { } /** Creates Paint objects once when first needed. */ - private void createPaints() { + public void createPaints() { if (bitmapPaint == null) { bitmapPaint = new Paint(); bitmapPaint.setAntiAlias(true); @@ -1314,13 +998,13 @@ private void createPaints() { if ((debugTextPaint == null || debugLinePaint == null) && debug) { debugTextPaint = new Paint(); - debugTextPaint.setTextSize(px(12)); + debugTextPaint.setTextSize(SubsamplingScaleImageViewDrawHelper.px(this, 12)); debugTextPaint.setColor(Color.MAGENTA); debugTextPaint.setStyle(Style.FILL); debugLinePaint = new Paint(); debugLinePaint.setColor(Color.MAGENTA); debugLinePaint.setStyle(Style.STROKE); - debugLinePaint.setStrokeWidth(px(1)); + debugLinePaint.setStrokeWidth(SubsamplingScaleImageViewDrawHelper.px(this, 1)); } } @@ -1328,11 +1012,11 @@ private void createPaints() { * Called on first draw when the view has dimensions. Calculates the initial sample size and * starts async loading of the base layer image - the whole source subsampled as necessary. */ - private synchronized void initialiseBaseLayer(@NonNull Point maxTileDimensions) { + public synchronized void initialiseBaseLayer(@NonNull Point maxTileDimensions) { debug("initialiseBaseLayer maxTileDimensions=%dx%d", maxTileDimensions.x, maxTileDimensions.y); satTemp = new ScaleAndTranslate(0f, new PointF(0, 0)); - fitToBounds(true, satTemp); + SubsamplingScaleImageViewDrawHelper.fitToBounds(this, true, satTemp); // Load double resolution - next level will be split into four tiles and at the center all // four are required, @@ -1431,7 +1115,7 @@ private boolean tileVisible(Tile tile) { } /** Sets scale and translate ready for the next draw. */ - private void preDraw() { + public void preDraw() { if (getWidth() == 0 || getHeight() == 0 || sWidth <= 0 || sHeight <= 0) { return; } @@ -1456,7 +1140,7 @@ private void preDraw() { } /** Calculates sample size to fit the source image in given bounds. */ - private int calculateInSampleSize(float scale) { + public int calculateInSampleSize(float scale) { if (minimumTileDpi > 0) { DisplayMetrics metrics = getResources().getDisplayMetrics(); float averageDpi = (metrics.xdpi + metrics.ydpi) / 2; @@ -1504,51 +1188,6 @@ private int calculateInSampleSize(float scale) { * @param sat The scale we want and the translation we're aiming for. The values are adjusted to * be valid. */ - void fitToBounds(boolean center, ScaleAndTranslate sat) { - if (panLimit == PAN_LIMIT_OUTSIDE && isReady()) { - center = false; - } - - PointF vTranslate = sat.vTranslate; - float scale = limitedScale(sat.scale); - float scaleWidth = scale * sWidth(); - float scaleHeight = scale * sHeight(); - - if (panLimit == PAN_LIMIT_CENTER && isReady()) { - vTranslate.x = Math.max(vTranslate.x, getWidth() / 2.0f - scaleWidth); - vTranslate.y = Math.max(vTranslate.y, getHeight() / 2.0f - scaleHeight); - } else if (center) { - vTranslate.x = Math.max(vTranslate.x, getWidth() - scaleWidth); - vTranslate.y = Math.max(vTranslate.y, getHeight() - scaleHeight); - } else { - vTranslate.x = Math.max(vTranslate.x, -scaleWidth); - vTranslate.y = Math.max(vTranslate.y, -scaleHeight); - } - - // Asymmetric padding adjustments - float xPaddingRatio = getPaddingLeft() > 0 || getPaddingRight() > 0 ? getPaddingLeft() / (float) (getPaddingLeft() + getPaddingRight()) : 0.5f; - float yPaddingRatio = getPaddingTop() > 0 || getPaddingBottom() > 0 ? getPaddingTop() / (float) (getPaddingTop() + getPaddingBottom()) : 0.5f; - - float maxTx; - float maxTy; - - if (panLimit == PAN_LIMIT_CENTER && isReady()) { - maxTx = Math.max(0, getWidth() / 2); - maxTy = Math.max(0, getHeight() / 2); - } else if (center) { - maxTx = Math.max(0, (getWidth() - scaleWidth) * xPaddingRatio); - maxTy = Math.max(0, (getHeight() - scaleHeight) * yPaddingRatio); - } else { - maxTx = Math.max(0, getWidth()); - maxTy = Math.max(0, getHeight()); - } - - vTranslate.x = Math.min(vTranslate.x, maxTx); - vTranslate.y = Math.min(vTranslate.y, maxTy); - - sat.scale = scale; - } - /** * Adjusts current scale and translate values to keep scale within the allowed range and the * image on screen. Minimum scale is set so one dimension fills the view and the image is @@ -1571,7 +1210,7 @@ public void fitToBounds(boolean center) { satTemp.scale = scale; satTemp.vTranslate.set(vTranslate); - fitToBounds(center, satTemp); + SubsamplingScaleImageViewDrawHelper.fitToBounds(this, center, satTemp); scale = satTemp.scale; vTranslate.set(satTemp.vTranslate); if (init && minimumScaleType != SCALE_TYPE_START) { @@ -2031,20 +1670,19 @@ private int getExifOrientation(Context context, String sourceUri) { private void execute(AsyncTask asyncTask) { asyncTask.executeOnExecutor(executor); } - - private static class Tile { - private Rect sRect; - private int sampleSize; - private Bitmap bitmap; - private boolean loading; - private boolean visible; + public static class Tile { + public Rect sRect; + public int sampleSize; + public Bitmap bitmap; + public boolean loading; + public boolean visible; // Volatile fields instantiated once then updated before use to reduce GC. - private Rect vRect; - private Rect fileSRect; + public Rect vRect; + public Rect fileSRect; } - static class Anim { + public static class Anim { float scaleStart; // Scale at start of anim float scaleEnd; // Scale at end of anim (target) PointF sCenterStart; // Source center point at start @@ -2060,14 +1698,14 @@ static class Anim { OnAnimationEventListener listener; // Event listener } - static class ScaleAndTranslate { + public static class ScaleAndTranslate { ScaleAndTranslate(float scale, PointF vTranslate) { this.scale = scale; this.vTranslate = vTranslate; } - float scale; - final PointF vTranslate; + public float scale; + public final PointF vTranslate; } /** Set scale, center and orientation from saved state. */ @@ -2110,7 +1748,7 @@ public void setMaxTileSize(int maxPixelsX, int maxPixelsY) { * tiling. */ @NonNull - private Point getMaxBitmapDimensions(Canvas canvas) { + public Point getMaxBitmapDimensions(Canvas canvas) { return new Point(Math.min(canvas.getMaximumBitmapWidth(), maxTileWidth), Math.min(canvas.getMaximumBitmapHeight(), maxTileHeight)); } @@ -2144,7 +1782,7 @@ public int sHeight() { */ @SuppressWarnings("SuspiciousNameCombination") @AnyThread - private void fileSRect(Rect sRect, Rect target) { + public void fileSRect(Rect sRect, Rect target) { if (getRequiredRotation() == 0) { target.set(sRect); } else if (getRequiredRotation() == 90) { @@ -2164,7 +1802,7 @@ private void fileSRect(Rect sRect, Rect target) { * Determines the rotation to be applied to tiles, based on EXIF orientation or chosen setting. */ @AnyThread - private int getRequiredRotation() { + public int getRequiredRotation() { if (orientation == ORIENTATION_USE_EXIF) { return sOrientation; } else { @@ -2194,7 +1832,7 @@ public void recycle() { } /** Convert screen to source x coordinate. */ - private float viewToSourceX(float vx) { + public float viewToSourceX(float vx) { if (vTranslate == null) { return Float.NaN; } @@ -2203,7 +1841,7 @@ private float viewToSourceX(float vx) { } /** Convert screen to source y coordinate. */ - private float viewToSourceY(float vy) { + public float viewToSourceY(float vy) { if (vTranslate == null) { return Float.NaN; } @@ -2307,7 +1945,7 @@ public final PointF viewToSourceCoord(float vx, float vy, @NonNull PointF sTarge } /** Convert source to view x coordinate. */ - private float sourceToViewX(float sx) { + public float sourceToViewX(float sx) { if (vTranslate == null) { return Float.NaN; } @@ -2316,7 +1954,7 @@ private float sourceToViewX(float sx) { } /** Convert source to view y coordinate. */ - private float sourceToViewY(float sy) { + public float sourceToViewY(float sy) { if (vTranslate == null) { return Float.NaN; } @@ -2379,7 +2017,7 @@ public final PointF sourceToViewCoord(float sx, float sy, @NonNull PointF vTarge } /** Convert source rect to screen rect, integer values. */ - private void sourceToViewRect(@NonNull Rect sRect, @NonNull Rect vTarget) { + public void sourceToViewRect(@NonNull Rect sRect, @NonNull Rect vTarget) { vTarget.set((int) sourceToViewX(sRect.left), (int) sourceToViewY(sRect.top), (int) sourceToViewX(sRect.right), (int) sourceToViewY(sRect.bottom)); } @@ -2390,7 +2028,7 @@ private void sourceToViewRect(@NonNull Rect sRect, @NonNull Rect vTarget) { * the image point as near to the screen center as permitted. */ @NonNull - private PointF vTranslateForSCenter(float sCenterX, float sCenterY, float scale) { + public PointF vTranslateForSCenter(float sCenterX, float sCenterY, float scale) { int vxCenter = getPaddingLeft() + (getWidth() - getPaddingRight() - getPaddingLeft()) / 2; int vyCenter = getPaddingTop() + (getHeight() - getPaddingBottom() - getPaddingTop()) / 2; @@ -2400,7 +2038,7 @@ private PointF vTranslateForSCenter(float sCenterX, float sCenterY, float scale) satTemp.scale = scale; satTemp.vTranslate.set(vxCenter - (sCenterX * scale), vyCenter - (sCenterY * scale)); - fitToBounds(true, satTemp); + SubsamplingScaleImageViewDrawHelper.fitToBounds(this, true, satTemp); return satTemp.vTranslate; } @@ -2436,7 +2074,7 @@ public float minScale() { } /** Adjust a requested scale to be within the allowed limits. */ - float limitedScale(float targetScale) { + public float limitedScale(float targetScale) { targetScale = Math.max(minScale(), targetScale); targetScale = Math.min(maxScale, targetScale); @@ -2453,7 +2091,7 @@ float limitedScale(float targetScale) { * @param duration Anm duration * @return Current value */ - private float ease(int type, long time, float from, float change, long duration) { + public float ease(int type, long time, float from, float change, long duration) { switch (type) { case EASE_IN_OUT_QUAD: return easeInOutQuad(time, from, change, duration); @@ -2473,7 +2111,7 @@ private float ease(int type, long time, float from, float change, long duration) * @param duration Anm duration * @return Current value */ - private float easeOutQuad(long time, float from, float change, long duration) { + public float easeOutQuad(long time, float from, float change, long duration) { float progress = (float) time / (float) duration; return -change * progress * (progress - 2) + from; @@ -2489,7 +2127,7 @@ private float easeOutQuad(long time, float from, float change, long duration) { * @param duration Anm duration * @return Current value */ - private float easeInOutQuad(long time, float from, float change, long duration) { + public float easeInOutQuad(long time, float from, float change, long duration) { float timeF = time / (duration / 2f); if (timeF < 1) { @@ -2502,16 +2140,12 @@ private float easeInOutQuad(long time, float from, float change, long duration) /** Debug logger */ @AnyThread - private void debug(String message, Object... args) { + public void debug(String message, Object... args) { if (debug) { Log.d(TAG, String.format(message, args)); } } - /** For debug overlays. Scale pixel value according to screen density. */ - private int px(int px) { - return (int) (density * px); - } /** * Swap the default region decoder implementation for one of your own. You must do this before @@ -3095,7 +2729,7 @@ public void setOnStateChangedListener(OnStateChangedListener onStateChangedListe this.onStateChangedListener = onStateChangedListener; } - private void sendStateChanged(float oldScale, PointF oldVTranslate, int origin) { + public void sendStateChanged(float oldScale, PointF oldVTranslate, int origin) { if (onStateChangedListener != null && scale != oldScale) { onStateChangedListener.onScaleChanged(scale, origin); } diff --git a/app/src/main/java/me/edgan/redditslide/util/SubsamplingScaleImageViewDrawHelper.java b/app/src/main/java/me/edgan/redditslide/util/SubsamplingScaleImageViewDrawHelper.java new file mode 100644 index 000000000..14af93274 --- /dev/null +++ b/app/src/main/java/me/edgan/redditslide/util/SubsamplingScaleImageViewDrawHelper.java @@ -0,0 +1,398 @@ +package me.edgan.redditslide.util; + +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Matrix; +import android.graphics.PointF; +import android.graphics.RectF; +import android.util.Log; + +import java.util.List; +import java.util.Locale; +import java.util.Map; + +import me.edgan.redditslide.Views.SubsamplingScaleImageView; + +/** + * Helper class containing drawing logic extracted from SubsamplingScaleImageView. + */ +public class SubsamplingScaleImageViewDrawHelper { + + private static final String TAG = SubsamplingScaleImageViewDrawHelper.class.getSimpleName(); + + public static void drawContent(SubsamplingScaleImageView view, Canvas canvas) { + view.createPaints(); + + // If image or view dimensions are not known yet, abort. + if (view.sWidth == 0 || view.sHeight == 0 || view.getWidth() == 0 || view.getHeight() == 0) { + return; + } + + // When using tiles, on first render with no tile map ready, initialise it and kick off async base image loading. + if (view.tileMap == null && view.decoder != null) { + view.initialiseBaseLayer(view.getMaxBitmapDimensions(canvas)); + } + + // If image has been loaded or supplied as a bitmap, onDraw may be the first time the view + // has dimensions and therefore the first opportunity to set scale and translate. If this call + // returns false there is nothing to be drawn so return immediately. + if (!view.checkReady()) { + return; + } + + // Set scale and translate before draw. + view.preDraw(); + + // If animating scale, calculate current scale and center with easing equations + if (view.anim != null && view.anim.vFocusStart != null) { + // Store current values so we can send an event if they change + float scaleBefore = view.scale; + if (view.vTranslateBefore == null) { + view.vTranslateBefore = new PointF(0, 0); + } + view.vTranslateBefore.set(view.vTranslate); + + long scaleElapsed = System.currentTimeMillis() - view.anim.time; + boolean finished = scaleElapsed > view.anim.duration; + scaleElapsed = Math.min(scaleElapsed, view.anim.duration); + view.scale = view.ease(view.anim.easing, scaleElapsed, view.anim.scaleStart, view.anim.scaleEnd - view.anim.scaleStart, view.anim.duration); + + // Apply required animation to the focal point + float vFocusNowX = view.ease(view.anim.easing, scaleElapsed, view.anim.vFocusStart.x, view.anim.vFocusEnd.x - view.anim.vFocusStart.x, view.anim.duration); + float vFocusNowY = view.ease(view.anim.easing, scaleElapsed, view.anim.vFocusStart.y, view.anim.vFocusEnd.y - view.anim.vFocusStart.y, view.anim.duration); + + // Find out where the focal point is at this scale and adjust its position to follow the animation path + view.vTranslate.x -= view.sourceToViewX(view.anim.sCenterEnd.x) - vFocusNowX; + view.vTranslate.y -= view.sourceToViewY(view.anim.sCenterEnd.y) - vFocusNowY; + + // For translate anims, showing the image non-centered is never allowed, for scaling anims it is during the animation. + view.fitToBounds(finished || (view.anim.scaleStart == view.anim.scaleEnd)); + view.sendStateChanged(scaleBefore, view.vTranslateBefore, view.anim.origin); + view.refreshRequiredTiles(finished); + + if (finished) { + if (view.anim.listener != null) { + try { + view.anim.listener.onComplete(); + } catch (Exception e) { + Log.w(TAG, "Error thrown by animation listener", e); + } + } + view.anim = null; + } + + view.invalidate(); + } + + if (view.tileMap != null && view.isBaseLayerReady()) { + // Optimum sample size for current scale + int sampleSize = Math.min(view.fullImageSampleSize, view.calculateInSampleSize(view.scale)); + + // First check for missing tiles - if there are any we need the base layer underneath to + // avoid gaps + boolean hasMissingTiles = false; + + for (Map.Entry> tileMapEntry : view.tileMap.entrySet()) { + if (tileMapEntry.getKey() == sampleSize) { + for (SubsamplingScaleImageView.Tile tile : tileMapEntry.getValue()) { + if (tile.visible && (tile.loading || tile.bitmap == null)) { + hasMissingTiles = true; + break; + } + } + } + } + + // Render all loaded tiles. LinkedHashMap used for bottom up rendering - lower res tiles + // underneath. + for (Map.Entry> tileMapEntry : view.tileMap.entrySet()) { + if (tileMapEntry.getKey() == sampleSize || hasMissingTiles) { + for (SubsamplingScaleImageView.Tile tile : tileMapEntry.getValue()) { + view.sourceToViewRect(tile.sRect, tile.vRect); + if (!tile.loading && tile.bitmap != null) { + if (view.tileBgPaint != null) { + canvas.drawRect(tile.vRect, view.tileBgPaint); + } + + if (view.matrix == null) { + view.matrix = new Matrix(); + } + + view.matrix.reset(); + setMatrixArray( + view.srcArray, + 0, + 0, + tile.bitmap.getWidth(), + 0, + tile.bitmap.getWidth(), + tile.bitmap.getHeight(), + 0, + tile.bitmap.getHeight()); + + if (view.getRequiredRotation() == SubsamplingScaleImageView.ORIENTATION_0) { + setMatrixArray( + view.dstArray, + tile.vRect.left, + tile.vRect.top, + tile.vRect.right, + tile.vRect.top, + tile.vRect.right, + tile.vRect.bottom, + tile.vRect.left, + tile.vRect.bottom); + } else if (view.getRequiredRotation() == SubsamplingScaleImageView.ORIENTATION_90) { + setMatrixArray( + view.dstArray, + tile.vRect.right, + tile.vRect.top, + tile.vRect.right, + tile.vRect.bottom, + tile.vRect.left, + tile.vRect.bottom, + tile.vRect.left, + tile.vRect.top); + } else if (view.getRequiredRotation() == SubsamplingScaleImageView.ORIENTATION_180) { + setMatrixArray( + view.dstArray, + tile.vRect.right, + tile.vRect.bottom, + tile.vRect.left, + tile.vRect.bottom, + tile.vRect.left, + tile.vRect.top, + tile.vRect.right, + tile.vRect.top); + } else if (view.getRequiredRotation() == SubsamplingScaleImageView.ORIENTATION_270) { + setMatrixArray( + view.dstArray, + tile.vRect.left, + tile.vRect.bottom, + tile.vRect.left, + tile.vRect.top, + tile.vRect.right, + tile.vRect.top, + tile.vRect.right, + tile.vRect.bottom); + } + + view.matrix.setPolyToPoly(view.srcArray, 0, view.dstArray, 0, 4); + canvas.drawBitmap(tile.bitmap, view.matrix, view.bitmapPaint); + + if (view.debug) { + canvas.drawRect(tile.vRect, view.debugLinePaint); + } + } else if (tile.loading && view.debug) { + canvas.drawText( + "LOADING", + tile.vRect.left + px(view, 5), + tile.vRect.top + px(view, 35), + view.debugTextPaint); + } + + if (tile.visible && view.debug) { + canvas.drawText( + "ISS " + + tile.sampleSize + + " RECT " + + tile.sRect.top + + "," + + tile.sRect.left + + "," + + tile.sRect.bottom + + "," + + tile.sRect.right, + tile.vRect.left + px(view, 5), + tile.vRect.top + px(view, 15), + view.debugTextPaint); + } + } + } + } + } else if (view.bitmap != null && !view.bitmap.isRecycled()) { + + float xScale = view.scale, yScale = view.scale; + + if (view.bitmapIsPreview) { + xScale = view.scale * ((float) view.sWidth / view.bitmap.getWidth()); + yScale = view.scale * ((float) view.sHeight / view.bitmap.getHeight()); + } + + if (view.matrix == null) { + view.matrix = new Matrix(); + } + + view.matrix.reset(); + view.matrix.postScale(xScale, yScale); + view.matrix.postRotate(view.getRequiredRotation()); + view.matrix.postTranslate(view.vTranslate.x, view.vTranslate.y); + + if (view.getRequiredRotation() == SubsamplingScaleImageView.ORIENTATION_180) { + view.matrix.postTranslate(view.scale * view.sWidth, view.scale * view.sHeight); + } else if (view.getRequiredRotation() == SubsamplingScaleImageView.ORIENTATION_90) { + view.matrix.postTranslate(view.scale * view.sHeight, 0); + } else if (view.getRequiredRotation() == SubsamplingScaleImageView.ORIENTATION_270) { + view.matrix.postTranslate(0, view.scale * view.sWidth); + } + + if (view.tileBgPaint != null) { + if (view.sRect == null) { + view.sRect = new RectF(); + } + view.sRect.set( + 0f, + 0f, + view.bitmapIsPreview ? view.bitmap.getWidth() : view.sWidth, + view.bitmapIsPreview ? view.bitmap.getHeight() : view.sHeight); + view.matrix.mapRect(view.sRect); + canvas.drawRect(view.sRect, view.tileBgPaint); + } + canvas.drawBitmap(view.bitmap, view.matrix, view.bitmapPaint); + } + + if (view.debug) { + canvas.drawText( + "Scale: " + + String.format(Locale.ENGLISH, "%.2f", view.scale) + + " (" + + String.format(Locale.ENGLISH, "%.2f", view.minScale()) + + " - " + + String.format(Locale.ENGLISH, "%.2f", view.maxScale) + + ")", + px(view, 5), + px(view, 15), + view.debugTextPaint); + canvas.drawText( + "Translate: " + + String.format(Locale.ENGLISH, "%.2f", view.vTranslate.x) + + ":" + + String.format(Locale.ENGLISH, "%.2f", view.vTranslate.y), + px(view, 5), + px(view, 30), + view.debugTextPaint); + PointF center = view.getCenter(); + // noinspection ConstantConditions + canvas.drawText( + "Source center: " + + String.format(Locale.ENGLISH, "%.2f", center.x) + + ":" + + String.format(Locale.ENGLISH, "%.2f", center.y), + px(view, 5), + px(view, 45), + view.debugTextPaint); + if (view.anim != null) { + PointF vCenterStart = view.sourceToViewCoord(view.anim.sCenterStart); + PointF vCenterEndRequested = view.sourceToViewCoord(view.anim.sCenterEndRequested); + PointF vCenterEnd = view.sourceToViewCoord(view.anim.sCenterEnd); + // noinspection ConstantConditions + canvas.drawCircle(vCenterStart.x, vCenterStart.y, px(view, 10), view.debugLinePaint); + view.debugLinePaint.setColor(Color.RED); + // noinspection ConstantConditions + canvas.drawCircle( + vCenterEndRequested.x, vCenterEndRequested.y, px(view, 20), view.debugLinePaint); + view.debugLinePaint.setColor(Color.BLUE); + // noinspection ConstantConditions + canvas.drawCircle(vCenterEnd.x, vCenterEnd.y, px(view, 25), view.debugLinePaint); + view.debugLinePaint.setColor(Color.CYAN); + canvas.drawCircle(view.getWidth() / 2.0f, view.getHeight() / 2.0f, px(view, 30), view.debugLinePaint); + } + if (view.vCenterStart != null) { + view.debugLinePaint.setColor(Color.RED); + canvas.drawCircle(view.vCenterStart.x, view.vCenterStart.y, px(view, 20), view.debugLinePaint); + } + if (view.quickScaleSCenter != null) { + view.debugLinePaint.setColor(Color.BLUE); + canvas.drawCircle(view.sourceToViewX(view.quickScaleSCenter.x), view.sourceToViewY(view.quickScaleSCenter.y), px(view, 35), view.debugLinePaint); + } + if (view.quickScaleVStart != null && view.isQuickScaling) { + view.debugLinePaint.setColor(Color.CYAN); + canvas.drawCircle(view.quickScaleVStart.x, view.quickScaleVStart.y, px(view, 30), view.debugLinePaint); + } + view.debugLinePaint.setColor(Color.MAGENTA); + } + } +/** For debug overlays. Scale pixel value according to screen density. */ +public static int px(SubsamplingScaleImageView view, int px) { // Made public + return (int) (view.density * px); +} + +/** Helper method for setting the values of a tile matrix array. */ +static void setMatrixArray( + float[] array, + float f0, + float f1, + float f2, + float f3, + float f4, + float f5, + float f6, + float f7) { + array[0] = f0; + array[1] = f1; + array[2] = f2; + array[3] = f3; + array[4] = f4; + array[5] = f5; + array[6] = f6; + array[7] = f7; +} + // Other helper methods will be added here. + + /** + * Adjusts hypothetical future scale and translate values to keep scale within the allowed range + * and the image on screen. Minimum scale is set so one dimension fills the view and the image + * is centered on the other dimension. Used to calculate what the target of an animation should + * be. + * + * @param view The SubsamplingScaleImageView instance. + * @param center Whether the image should be centered in the dimension it's too small to fill. + * While animating this can be false to avoid changes in direction as bounds are reached. + * @param sat The scale we want and the translation we're aiming for. The values are adjusted to + * be valid. + */ + public static void fitToBounds(SubsamplingScaleImageView view, boolean center, SubsamplingScaleImageView.ScaleAndTranslate sat) { + if (view.panLimit == SubsamplingScaleImageView.PAN_LIMIT_OUTSIDE && view.isReady()) { + center = false; + } + + PointF vTranslate = sat.vTranslate; + // Use view's limitedScale method - ensure it's accessible (made public in next step) + float scale = view.limitedScale(sat.scale); + float scaleWidth = scale * view.sWidth(); + float scaleHeight = scale * view.sHeight(); + + if (view.panLimit == SubsamplingScaleImageView.PAN_LIMIT_CENTER && view.isReady()) { + vTranslate.x = Math.max(vTranslate.x, view.getWidth() / 2.0f - scaleWidth); + vTranslate.y = Math.max(vTranslate.y, view.getHeight() / 2.0f - scaleHeight); + } else if (center) { + vTranslate.x = Math.max(vTranslate.x, view.getWidth() - scaleWidth); + vTranslate.y = Math.max(vTranslate.y, view.getHeight() - scaleHeight); + } else { + vTranslate.x = Math.max(vTranslate.x, -scaleWidth); + vTranslate.y = Math.max(vTranslate.y, -scaleHeight); + } + + // Asymmetric padding adjustments + float xPaddingRatio = view.getPaddingLeft() > 0 || view.getPaddingRight() > 0 ? view.getPaddingLeft() / (float) (view.getPaddingLeft() + view.getPaddingRight()) : 0.5f; + float yPaddingRatio = view.getPaddingTop() > 0 || view.getPaddingBottom() > 0 ? view.getPaddingTop() / (float) (view.getPaddingTop() + view.getPaddingBottom()) : 0.5f; + + float maxTx; + float maxTy; + + if (view.panLimit == SubsamplingScaleImageView.PAN_LIMIT_CENTER && view.isReady()) { + maxTx = Math.max(0, view.getWidth() / 2); + maxTy = Math.max(0, view.getHeight() / 2); + } else if (center) { + maxTx = Math.max(0, (view.getWidth() - scaleWidth) * xPaddingRatio); + maxTy = Math.max(0, (view.getHeight() - scaleHeight) * yPaddingRatio); + } else { + maxTx = Math.max(0, view.getWidth()); + maxTy = Math.max(0, view.getHeight()); + } + + vTranslate.x = Math.min(vTranslate.x, maxTx); + vTranslate.y = Math.min(vTranslate.y, maxTy); + + sat.scale = scale; + } +} \ No newline at end of file From 72f288ca98117a7bb71db8dd762a607b463cd96b Mon Sep 17 00:00:00 2001 From: Nathan Grennan Date: Tue, 1 Apr 2025 18:14:52 -0700 Subject: [PATCH 19/99] Broke ImageViewGestureListener.java out of SubsamplingScaleImageView.java --- .../redditslide/Views/AnimationBuilder.java | 20 ++-- .../Views/SubsamplingScaleImageView.java | 108 ++++-------------- .../util/ImageViewGestureListener.java | 85 ++++++++++++++ 3 files changed, 120 insertions(+), 93 deletions(-) create mode 100644 app/src/main/java/me/edgan/redditslide/util/ImageViewGestureListener.java diff --git a/app/src/main/java/me/edgan/redditslide/Views/AnimationBuilder.java b/app/src/main/java/me/edgan/redditslide/Views/AnimationBuilder.java index d0632b6a5..4280df298 100644 --- a/app/src/main/java/me/edgan/redditslide/Views/AnimationBuilder.java +++ b/app/src/main/java/me/edgan/redditslide/Views/AnimationBuilder.java @@ -29,13 +29,12 @@ public final class AnimationBuilder { private SubsamplingScaleImageView.OnAnimationEventListener listener; // Reference static constants via class - private static final List VALID_EASING_STYLES = - Arrays.asList(SubsamplingScaleImageView.EASE_IN_OUT_QUAD, SubsamplingScaleImageView.EASE_OUT_QUAD); + private static final List VALID_EASING_STYLES = Arrays.asList(SubsamplingScaleImageView.EASE_IN_OUT_QUAD, SubsamplingScaleImageView.EASE_OUT_QUAD); - AnimationBuilder(SubsamplingScaleImageView view, PointF sCenter) { + public AnimationBuilder(SubsamplingScaleImageView view, PointF sCenter) { this.view = view; - this.targetScale = view.scale; // Access via view instance + this.targetScale = view.scale; this.targetSCenter = sCenter; this.vFocus = null; } @@ -43,7 +42,7 @@ public final class AnimationBuilder { AnimationBuilder(SubsamplingScaleImageView view, float scale) { this.view = view; this.targetScale = scale; - this.targetSCenter = view.getCenter(); // Access via view instance + this.targetSCenter = view.getCenter(); this.vFocus = null; } @@ -70,6 +69,7 @@ public final class AnimationBuilder { @NonNull public AnimationBuilder withDuration(long duration) { this.duration = duration; + return this; } @@ -82,6 +82,7 @@ public AnimationBuilder withDuration(long duration) { @NonNull public AnimationBuilder withInterruptible(boolean interruptible) { this.interruptible = interruptible; + return this; } @@ -97,7 +98,9 @@ public AnimationBuilder withEasing(int easing) { if (!VALID_EASING_STYLES.contains(easing)) { throw new IllegalArgumentException("Unknown easing type: " + easing); } + this.easing = easing; + return this; } @@ -110,6 +113,7 @@ public AnimationBuilder withEasing(int easing) { @NonNull public AnimationBuilder withOnAnimationEventListener(SubsamplingScaleImageView.OnAnimationEventListener listener) { this.listener = listener; + return this; } @@ -120,15 +124,17 @@ public AnimationBuilder withOnAnimationEventListener(SubsamplingScaleImageView.O * is reached. The latter behaviour is used for flings but nothing else. */ @NonNull - AnimationBuilder withPanLimited(boolean panLimited) { + public AnimationBuilder withPanLimited(boolean panLimited) { this.panLimited = panLimited; + return this; } /** Only for internal use. Indicates what caused the animation. */ @NonNull - AnimationBuilder withOrigin(int origin) { + public AnimationBuilder withOrigin(int origin) { this.origin = origin; + return this; } diff --git a/app/src/main/java/me/edgan/redditslide/Views/SubsamplingScaleImageView.java b/app/src/main/java/me/edgan/redditslide/Views/SubsamplingScaleImageView.java index 76b58a1be..54f3fe47e 100644 --- a/app/src/main/java/me/edgan/redditslide/Views/SubsamplingScaleImageView.java +++ b/app/src/main/java/me/edgan/redditslide/Views/SubsamplingScaleImageView.java @@ -44,6 +44,7 @@ import com.davemorrissey.labs.subscaleview.decoder.SkiaImageRegionDecoder; import me.edgan.redditslide.SettingValues; +import me.edgan.redditslide.util.ImageViewGestureListener; import me.edgan.redditslide.util.SubsamplingScaleImageViewDrawHelper; import me.edgan.redditslide.util.TouchEventUtil; @@ -681,80 +682,15 @@ private void reset(boolean newImage) { setGestureDetector(getContext()); } - private void setGestureDetector(final Context context) { - this.detector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() { - @Override - public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { - if (panEnabled - && readySent - && vTranslate != null - && e1 != null - && e2 != null - && (Math.abs(e1.getX() - e2.getX()) > 50 - || Math.abs(e1.getY() - e2.getY()) > 50) - && (Math.abs(velocityX) > 500 || Math.abs(velocityY) > 500) - && !isZooming) { - PointF vTranslateEnd = - new PointF( - vTranslate.x + (velocityX * 0.25f), - vTranslate.y + (velocityY * 0.25f)); - float sCenterXEnd = - ((getWidth() / 2.0f) - vTranslateEnd.x) / scale; - float sCenterYEnd = - ((getHeight() / 2.0f) - vTranslateEnd.y) / scale; - new AnimationBuilder(SubsamplingScaleImageView.this, new PointF(sCenterXEnd, sCenterYEnd)) - .withEasing(EASE_OUT_QUAD) - .withPanLimited(false) - .withOrigin(ORIGIN_FLING) - .start(); - return true; - } - return super.onFling(e1, e2, velocityX, velocityY); - } - - @Override - public boolean onSingleTapConfirmed(MotionEvent e) { - performClick(); - return true; - } + // Public method to reset the gesture detector, used by the listener if needed + public void setGestureDetectorPublic(final Context context) { + setGestureDetector(context); + } - @Override - public boolean onDoubleTap(MotionEvent e) { - if (zoomEnabled && readySent && vTranslate != null) { - // Hacky solution for #15 - after a double tap the - // GestureDetector gets in a state - // where the next fling is ignored, so here we replace it with a - // new one. - setGestureDetector(context); - if (quickScaleEnabled) { - // Store quick scale params. This will become either a - // double tap zoom or a - // quick scale depending on whether the user swipes. - vCenterStart = new PointF(e.getX(), e.getY()); - vTranslateStart = new PointF(vTranslate.x, vTranslate.y); - scaleStart = scale; - isQuickScaling = true; - isZooming = true; - quickScaleLastDistance = -1F; - quickScaleSCenter = viewToSourceCoord(vCenterStart); - quickScaleVStart = new PointF(e.getX(), e.getY()); - quickScaleVLastPoint = - new PointF( - quickScaleSCenter.x, quickScaleSCenter.y); - quickScaleMoved = false; - // We need to get events in onTouchEvent after this. - return false; - } else { - // Start double tap zoom animation. - doubleTapZoom( - viewToSourceCoord(new PointF(e.getX(), e.getY())), - new PointF(e.getX(), e.getY())); - return true; - } - } - return super.onDoubleTapEvent(e); - } - }); + private void setGestureDetector(final Context context) { + // Use the new external listener class directly. + // The methods onSingleTapConfirmed and onDoubleTap will be moved next. + this.detector = new GestureDetector(context, new ImageViewGestureListener(this, context)); singleDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() { @Override @@ -1683,19 +1619,19 @@ public static class Tile { } public static class Anim { - float scaleStart; // Scale at start of anim - float scaleEnd; // Scale at end of anim (target) - PointF sCenterStart; // Source center point at start - PointF sCenterEnd; // Source center point at end, adjusted for pan limits - PointF sCenterEndRequested; // Source center point that was requested, without adjustment - PointF vFocusStart; // View point that was double tapped - PointF vFocusEnd; // Where the view focal point should be moved to during the anim - long duration = 500; // How long the anim takes - boolean interruptible = true; // Whether the anim can be interrupted by a touch - int easing = EASE_IN_OUT_QUAD; // Easing style - int origin = ORIGIN_ANIM; // Animation origin (API, double tap or fling) - long time = System.currentTimeMillis(); // Start time - OnAnimationEventListener listener; // Event listener + public float scaleStart; // Scale at start of anim + public float scaleEnd; // Scale at end of anim (target) + public PointF sCenterStart; // Source center point at start + public PointF sCenterEnd; // Source center point at end, adjusted for pan limits + public PointF sCenterEndRequested; // Source center point that was requested, without adjustment + public PointF vFocusStart; // View point that was double tapped + public PointF vFocusEnd; // Where the view focal point should be moved to during the anim + public long duration = 500; // How long the anim takes + public boolean interruptible = true; // Whether the anim can be interrupted by a touch + public int easing = EASE_IN_OUT_QUAD; // Easing style + public int origin = ORIGIN_ANIM; // Animation origin (API, double tap or fling) + public long time = System.currentTimeMillis(); // Start time + public OnAnimationEventListener listener; // Event listener } public static class ScaleAndTranslate { diff --git a/app/src/main/java/me/edgan/redditslide/util/ImageViewGestureListener.java b/app/src/main/java/me/edgan/redditslide/util/ImageViewGestureListener.java new file mode 100644 index 000000000..4f9ef070f --- /dev/null +++ b/app/src/main/java/me/edgan/redditslide/util/ImageViewGestureListener.java @@ -0,0 +1,85 @@ +package me.edgan.redditslide.util; + +import android.content.Context; +import android.graphics.PointF; +import android.view.GestureDetector; +import android.view.MotionEvent; + +import me.edgan.redditslide.Views.AnimationBuilder; +import me.edgan.redditslide.Views.SubsamplingScaleImageView; + +public class ImageViewGestureListener extends GestureDetector.SimpleOnGestureListener { + + private static final String TAG = ImageViewGestureListener.class.getSimpleName(); + + private final SubsamplingScaleImageView view; + private final Context context; + + public ImageViewGestureListener(SubsamplingScaleImageView view, Context context) { + this.view = view; + this.context = context; + } + + @Override + public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { + if (view.panEnabled + && view.readySent && view.vTranslate != null && e1 != null && e2 != null + && (Math.abs(e1.getX() - e2.getX()) > 50 || Math.abs(e1.getY() - e2.getY()) > 50) + && (Math.abs(velocityX) > 500 || Math.abs(velocityY) > 500) + && !view.isZooming) { + PointF vTranslateEnd = new PointF(view.vTranslate.x + (velocityX * 0.25f), view.vTranslate.y + (velocityY * 0.25f)); + float sCenterXEnd = ((view.getWidth() / 2.0f) - vTranslateEnd.x) / view.scale; + float sCenterYEnd = ((view.getHeight() / 2.0f) - vTranslateEnd.y) / view.scale; + new AnimationBuilder(view, new PointF(sCenterXEnd, sCenterYEnd)) + .withEasing(SubsamplingScaleImageView.EASE_OUT_QUAD) + .withPanLimited(false) + .withOrigin(SubsamplingScaleImageView.ORIGIN_FLING) + .start(); + + return true; + } + + return super.onFling(e1, e2, velocityX, velocityY); + } + + @Override + public boolean onSingleTapConfirmed(MotionEvent e) { + view.performClick(); // Call performClick on the view instance + + return true; + } + + @Override + public boolean onDoubleTap(MotionEvent e) { + if (view.zoomEnabled && view.readySent && view.vTranslate != null) { + // Hacky solution for #15 - after a double tap the GestureDetector gets in a state where the next fling is ignored, so here we replace it with a new one. + // Use the public method we added to the view. + view.setGestureDetectorPublic(context); + + if (view.quickScaleEnabled) { + // Store quick scale params. This will become either a double tap zoom or a quick scale depending on whether the user swipes. + view.vCenterStart = new PointF(e.getX(), e.getY()); + view.vTranslateStart = new PointF(view.vTranslate.x, view.vTranslate.y); + view.scaleStart = view.scale; + view.isQuickScaling = true; + view.isZooming = true; + view.quickScaleLastDistance = -1F; + view.quickScaleSCenter = view.viewToSourceCoord(view.vCenterStart); + view.quickScaleVStart = new PointF(e.getX(), e.getY()); + view.quickScaleVLastPoint = new PointF(view.quickScaleSCenter.x, view.quickScaleSCenter.y); + view.quickScaleMoved = false; + + // We need to get events in onTouchEvent after this. + return false; + } else { + // Start double tap zoom animation. + view.doubleTapZoom(view.viewToSourceCoord(new PointF(e.getX(), e.getY())), new PointF(e.getX(), e.getY())); + + return true; + } + } + + // Use onDoubleTap here, not onDoubleTapEvent + return super.onDoubleTap(e); + } +} \ No newline at end of file From dd1f5dcadc2ebb1933f1913b609518189b66f9dc Mon Sep 17 00:00:00 2001 From: Nathan Grennan Date: Tue, 1 Apr 2025 18:50:14 -0700 Subject: [PATCH 20/99] Broke TilesInitTask.java out of SubsamplingScaleImageView.java --- .../Views/SubsamplingScaleImageView.java | 88 ++---------------- .../edgan/redditslide/util/TilesInitTask.java | 90 +++++++++++++++++++ 2 files changed, 96 insertions(+), 82 deletions(-) create mode 100644 app/src/main/java/me/edgan/redditslide/util/TilesInitTask.java diff --git a/app/src/main/java/me/edgan/redditslide/Views/SubsamplingScaleImageView.java b/app/src/main/java/me/edgan/redditslide/Views/SubsamplingScaleImageView.java index 54f3fe47e..fca6e7d5c 100644 --- a/app/src/main/java/me/edgan/redditslide/Views/SubsamplingScaleImageView.java +++ b/app/src/main/java/me/edgan/redditslide/Views/SubsamplingScaleImageView.java @@ -57,6 +57,7 @@ import java.util.concurrent.Executor; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; +import me.edgan.redditslide.util.TilesInitTask; /** * Displays an image subsampled as necessary to avoid loading too much image data into memory. After @@ -271,7 +272,7 @@ public class SubsamplingScaleImageView extends View { public int sWidth; public int sHeight; public int sOrientation; - private Rect sRegion; + public Rect sRegion; private Rect pRegion; // Is two-finger zooming in progress @@ -324,7 +325,7 @@ public class SubsamplingScaleImageView extends View { public boolean imageLoadedSent; // Event listener - private OnImageEventListener onImageEventListener; + public OnImageEventListener onImageEventListener; // Scale and center listener private OnStateChangedListener onStateChangedListener; @@ -1213,84 +1214,9 @@ private void initialiseTileMap(Point maxTileDimensions) { } /** Async task used to get image details without blocking the UI thread. */ - private static class TilesInitTask extends AsyncTask { - private final WeakReference viewRef; - private final WeakReference contextRef; - private final WeakReference> decoderFactoryRef; - private final Uri source; - private ImageRegionDecoder decoder; - private Exception exception; - - TilesInitTask( - SubsamplingScaleImageView view, - Context context, - DecoderFactory decoderFactory, - Uri source) { - this.viewRef = new WeakReference<>(view); - this.contextRef = new WeakReference<>(context); - this.decoderFactoryRef = - new WeakReference>(decoderFactory); - this.source = source; - } - - @Override - protected int[] doInBackground(Void... params) { - try { - String sourceUri = source.toString(); - Context context = contextRef.get(); - DecoderFactory decoderFactory = - decoderFactoryRef.get(); - SubsamplingScaleImageView view = viewRef.get(); - if (context != null && decoderFactory != null && view != null) { - view.debug("TilesInitTask.doInBackground"); - Point dimensions; - try { - decoder = decoderFactory.make(); - dimensions = decoder.init(context, source); - } catch (Exception e) { - // switch to new decoder - cancel(true); - view.doLoader(true); - return null; - } - int sWidth = dimensions.x; - int sHeight = dimensions.y; - int exifOrientation = view.getExifOrientation(context, sourceUri); - if (view.sRegion != null) { - view.sRegion.left = Math.max(0, view.sRegion.left); - view.sRegion.top = Math.max(0, view.sRegion.top); - view.sRegion.right = Math.min(sWidth, view.sRegion.right); - view.sRegion.bottom = Math.min(sHeight, view.sRegion.bottom); - sWidth = view.sRegion.width(); - sHeight = view.sRegion.height(); - } - - return new int[] {sWidth, sHeight, exifOrientation}; - } - } catch (Exception e) { - Log.e(TAG, "Failed to initialise bitmap decoder", e); - this.exception = e; - } - return null; - } - - @Override - protected void onPostExecute(int[] xyo) { - if (xyo != null) { - final SubsamplingScaleImageView view = viewRef.get(); - if (view != null) { - if (decoder != null && xyo != null && xyo.length == 3) { - view.onTilesInited(decoder, xyo[0], xyo[1], xyo[2]); - } else if (exception != null && view.onImageEventListener != null) { - view.onImageEventListener.onImageLoadError(exception); - } - } - } - } - } /** Called by worker task when decoder is ready and image size and EXIF orientation is known. */ - private synchronized void onTilesInited(ImageRegionDecoder decoder, int sWidth, int sHeight, int sOrientation) { + public synchronized void onTilesInited(ImageRegionDecoder decoder, int sWidth, int sHeight, int sOrientation) { debug("onTilesInited sWidth=%d, sHeight=%d, sOrientation=%d", sWidth, sHeight, sOrientation); // If actual dimensions don't match the declared size, reset everything. @@ -1552,9 +1478,8 @@ private synchronized void onImageLoaded( * orientation. This will only work for external files, not assets, resources or other URIs. */ @AnyThread - private int getExifOrientation(Context context, String sourceUri) { + public int getExifOrientation(Context context, String sourceUri) { int exifOrientation = ORIENTATION_0; - if (sourceUri.startsWith(ContentResolver.SCHEME_CONTENT)) { Cursor cursor = null; try { @@ -1565,8 +1490,7 @@ private int getExifOrientation(Context context, String sourceUri) { if (cursor.moveToFirst()) { int orientation = cursor.getInt(0); - if (VALID_ORIENTATIONS.contains(orientation) - && orientation != ORIENTATION_USE_EXIF) { + if (VALID_ORIENTATIONS.contains(orientation) && orientation != ORIENTATION_USE_EXIF) { exifOrientation = orientation; } else { Log.w(TAG, "Unsupported orientation: " + orientation); diff --git a/app/src/main/java/me/edgan/redditslide/util/TilesInitTask.java b/app/src/main/java/me/edgan/redditslide/util/TilesInitTask.java new file mode 100644 index 000000000..8c5c22d2b --- /dev/null +++ b/app/src/main/java/me/edgan/redditslide/util/TilesInitTask.java @@ -0,0 +1,90 @@ +package me.edgan.redditslide.util; + +import android.content.Context; +import android.graphics.Point; +import android.net.Uri; +import android.os.AsyncTask; +import android.util.Log; + +import com.davemorrissey.labs.subscaleview.decoder.DecoderFactory; +import com.davemorrissey.labs.subscaleview.decoder.ImageRegionDecoder; + +import java.lang.ref.WeakReference; + +import me.edgan.redditslide.Views.SubsamplingScaleImageView; + +/** Async task used to get image details without blocking the UI thread. */ +public class TilesInitTask extends AsyncTask { + private static final String TAG = TilesInitTask.class.getSimpleName(); + private final WeakReference viewRef; + private final WeakReference contextRef; + private final WeakReference> decoderFactoryRef; + private final Uri source; + private ImageRegionDecoder decoder; + private Exception exception; + + public TilesInitTask(SubsamplingScaleImageView view, Context context, DecoderFactory decoderFactory, Uri source) { + this.viewRef = new WeakReference<>(view); + this.contextRef = new WeakReference<>(context); + this.decoderFactoryRef = new WeakReference>(decoderFactory); + this.source = source; + } + + @Override + protected int[] doInBackground(Void... params) { + try { + String sourceUri = source.toString(); + Context context = contextRef.get(); + DecoderFactory decoderFactory = decoderFactoryRef.get(); + SubsamplingScaleImageView view = viewRef.get(); + + if (context != null && decoderFactory != null && view != null) { + view.debug("TilesInitTask.doInBackground"); + Point dimensions; + + try { + decoder = decoderFactory.make(); + dimensions = decoder.init(context, source); + } catch (Exception e) { + // switch to new decoder + cancel(true); + view.doLoader(true); + return null; + } + + int sWidth = dimensions.x; + int sHeight = dimensions.y; + int exifOrientation = view.getExifOrientation(context, sourceUri); + + if (view.sRegion != null) { + view.sRegion.left = Math.max(0, view.sRegion.left); + view.sRegion.top = Math.max(0, view.sRegion.top); + view.sRegion.right = Math.min(sWidth, view.sRegion.right); + view.sRegion.bottom = Math.min(sHeight, view.sRegion.bottom); + sWidth = view.sRegion.width(); + sHeight = view.sRegion.height(); + } + + return new int[] {sWidth, sHeight, exifOrientation}; + } + } catch (Exception e) { + Log.e(TAG, "Failed to initialise bitmap decoder", e); + this.exception = e; + } + return null; + } + + @Override + protected void onPostExecute(int[] xyo) { + if (xyo != null) { + final SubsamplingScaleImageView view = viewRef.get(); + if (view != null) { + if (decoder != null && xyo != null && xyo.length == 3) { // Fixed && + view.onTilesInited(decoder, xyo[0], xyo[1], xyo[2]); + } else if (exception != null && view.onImageEventListener != null) { // Fixed && + view.onImageEventListener.onImageLoadError(exception); + } + } + } + } +} \ No newline at end of file From 2190d63f424173d0d5da75dbc298ba5ccb2e7a6e Mon Sep 17 00:00:00 2001 From: Nathan Grennan Date: Tue, 1 Apr 2025 19:09:28 -0700 Subject: [PATCH 21/99] Broke out SubsamplingScaleImageViewStateHelper.java of SubsamplingScaleImageView.java --- .../redditslide/Views/AnimationBuilder.java | 8 +- .../Views/SubsamplingScaleImageView.java | 406 +++--------------- .../util/ImageViewGestureListener.java | 4 +- .../SubsamplingScaleImageViewDrawHelper.java | 38 +- .../SubsamplingScaleImageViewStateHelper.java | 341 +++++++++++++++ .../redditslide/util/TouchEventUtil.java | 32 +- 6 files changed, 440 insertions(+), 389 deletions(-) create mode 100644 app/src/main/java/me/edgan/redditslide/util/SubsamplingScaleImageViewStateHelper.java diff --git a/app/src/main/java/me/edgan/redditslide/Views/AnimationBuilder.java b/app/src/main/java/me/edgan/redditslide/Views/AnimationBuilder.java index 4280df298..fc94bef3e 100644 --- a/app/src/main/java/me/edgan/redditslide/Views/AnimationBuilder.java +++ b/app/src/main/java/me/edgan/redditslide/Views/AnimationBuilder.java @@ -1,5 +1,7 @@ package me.edgan.redditslide.Views; +import me.edgan.redditslide.util.SubsamplingScaleImageViewStateHelper; + import android.graphics.PointF; import android.util.Log; import androidx.annotation.NonNull; @@ -150,8 +152,8 @@ public void start() { int vxCenter = view.getPaddingLeft() + (view.getWidth() - view.getPaddingRight() - view.getPaddingLeft()) / 2; int vyCenter = view.getPaddingTop() + (view.getHeight() - view.getPaddingBottom() - view.getPaddingTop()) / 2; - float targetScale = view.limitedScale(this.targetScale); - PointF targetSCenter = panLimited ? view.limitedSCenter(this.targetSCenter.x, this.targetSCenter.y, targetScale, new PointF()): this.targetSCenter; + float targetScale = SubsamplingScaleImageViewStateHelper.limitedScale(view, this.targetScale); + PointF targetSCenter = panLimited ? SubsamplingScaleImageViewStateHelper.limitedSCenter(view, this.targetSCenter.x, this.targetSCenter.y, targetScale, new PointF()): this.targetSCenter; // Use helper view.anim = new SubsamplingScaleImageView.Anim(); view.anim.scaleStart = view.scale; view.anim.scaleEnd = targetScale; @@ -159,7 +161,7 @@ public void start() { view.anim.sCenterEndRequested = targetSCenter; view.anim.sCenterStart = view.getCenter(); view.anim.sCenterEnd = targetSCenter; - view.anim.vFocusStart = view.sourceToViewCoord(targetSCenter); + view.anim.vFocusStart = SubsamplingScaleImageViewStateHelper.sourceToViewCoord(view, targetSCenter); view.anim.vFocusEnd = new PointF(vxCenter, vyCenter); view.anim.duration = duration; view.anim.interruptible = interruptible; diff --git a/app/src/main/java/me/edgan/redditslide/Views/SubsamplingScaleImageView.java b/app/src/main/java/me/edgan/redditslide/Views/SubsamplingScaleImageView.java index fca6e7d5c..bd26d4596 100644 --- a/app/src/main/java/me/edgan/redditslide/Views/SubsamplingScaleImageView.java +++ b/app/src/main/java/me/edgan/redditslide/Views/SubsamplingScaleImageView.java @@ -57,6 +57,7 @@ import java.util.concurrent.Executor; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; +import me.edgan.redditslide.util.SubsamplingScaleImageViewStateHelper; // Added import import me.edgan.redditslide.util.TilesInitTask; /** @@ -222,7 +223,7 @@ public class SubsamplingScaleImageView extends View { public float maxScale = 2F; // Min scale allowed (prevent infinite zoom) - private float minScale = minScale(); + private float minScale; // Defer initialization // Density to reach before loading higher resolution tiles private int minimumTileDpi = -1; @@ -360,6 +361,8 @@ public class SubsamplingScaleImageView extends View { public SubsamplingScaleImageView(Context context, AttributeSet attr) { super(context, attr); density = getResources().getDisplayMetrics().density; + // Initialize minScale here now that 'this' is available + this.minScale = SubsamplingScaleImageViewStateHelper.minScale(this); setMinimumDpi(160); setDoubleTapZoomDpi(160); setMinimumTileDpi(320); @@ -709,7 +712,7 @@ public boolean onSingleTapConfirmed(MotionEvent e) { @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { debug("onSizeChanged %dx%d -> %dx%d", oldw, oldh, w, h); - PointF sCenter = getCenter(); + PointF sCenter = SubsamplingScaleImageViewStateHelper.getCenter(this); // Use helper if (readySent && sCenter != null) { this.anim = null; this.pendingScale = scale; @@ -735,12 +738,12 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { if (sWidth > 0 && sHeight > 0) { if (resizeWidth && resizeHeight) { - width = sWidth(); - height = sHeight(); + width = SubsamplingScaleImageViewStateHelper.sWidth(this); // Use helper + height = SubsamplingScaleImageViewStateHelper.sHeight(this); // Use helper } else if (resizeHeight) { - height = (int) ((((double) sHeight() / (double) sWidth()) * width)); + height = (int) ((((double) SubsamplingScaleImageViewStateHelper.sHeight(this) / (double) SubsamplingScaleImageViewStateHelper.sWidth(this)) * width)); // Use helper } else if (resizeWidth) { - width = (int) ((((double) sWidth() / (double) sHeight()) * height)); + width = (int) ((((double) SubsamplingScaleImageViewStateHelper.sWidth(this) / (double) SubsamplingScaleImageViewStateHelper.sHeight(this)) * height)); // Use helper } } @@ -823,14 +826,14 @@ public void doubleTapZoom(PointF sCenter, PointF vFocus) { sCenter.y = sRequestedCenter.y; } else { // With no requested center, scale around the image center. - sCenter.x = sWidth() / 2.0f; - sCenter.y = sHeight() / 2.0f; + sCenter.x = SubsamplingScaleImageViewStateHelper.sWidth(this) / 2.0f; // Use helper + sCenter.y = SubsamplingScaleImageViewStateHelper.sHeight(this) / 2.0f; // Use helper } } float doubleTapZoomScale = Math.min(maxScale, this.doubleTapZoomScale); - boolean zoomIn = (scale <= doubleTapZoomScale * 0.9) || scale == minScale; - float targetScale = zoomIn ? doubleTapZoomScale : minScale(); + boolean zoomIn = (scale <= doubleTapZoomScale * 0.9) || scale == SubsamplingScaleImageViewStateHelper.minScale(this); // Use helper + float targetScale = zoomIn ? doubleTapZoomScale : SubsamplingScaleImageViewStateHelper.minScale(this); // Use helper if (doubleTapZoomStyle == ZOOM_FOCUS_CENTER_IMMEDIATE) { setScaleAndCenter(targetScale, sCenter); @@ -964,7 +967,7 @@ public synchronized void initialiseBaseLayer(@NonNull Point maxTileDimensions) { fullImageSampleSize /= 2; } - if (fullImageSampleSize == 1 && sRegion == null && sWidth() < maxTileDimensions.x && sHeight() < maxTileDimensions.y) { + if (fullImageSampleSize == 1 && sRegion == null && SubsamplingScaleImageViewStateHelper.sWidth(this) < maxTileDimensions.x && SubsamplingScaleImageViewStateHelper.sHeight(this) < maxTileDimensions.y) { // Use helper // Whole image is required at native resolution, and is smaller than the canvas max // bitmap size. // Use BitmapDecoder for better image support. @@ -1040,10 +1043,10 @@ public void refreshRequiredTiles(boolean load) { /** Determine whether tile is visible. */ private boolean tileVisible(Tile tile) { - float sVisLeft = viewToSourceX(0), - sVisRight = viewToSourceX(getWidth()), - sVisTop = viewToSourceY(0), - sVisBottom = viewToSourceY(getHeight()); + float sVisLeft = SubsamplingScaleImageViewStateHelper.viewToSourceX(this, 0), // Use helper + sVisRight = SubsamplingScaleImageViewStateHelper.viewToSourceX(this, getWidth()), // Use helper + sVisTop = SubsamplingScaleImageViewStateHelper.viewToSourceY(this, 0), // Use helper + sVisBottom = SubsamplingScaleImageViewStateHelper.viewToSourceY(this, getHeight()); // Use helper return !(sVisLeft > tile.sRect.right || tile.sRect.left > sVisRight @@ -1084,8 +1087,8 @@ public int calculateInSampleSize(float scale) { scale = (minimumTileDpi / averageDpi) * scale; } - int reqWidth = (int) (sWidth() * scale); - int reqHeight = (int) (sHeight() * scale); + int reqWidth = (int) (SubsamplingScaleImageViewStateHelper.sWidth(this) * scale); // Use helper + int reqHeight = (int) (SubsamplingScaleImageViewStateHelper.sHeight(this) * scale); // Use helper // Raw height and width of image int inSampleSize = 1; @@ -1093,11 +1096,11 @@ public int calculateInSampleSize(float scale) { return 32; } - if (sHeight() > reqHeight || sWidth() > reqWidth) { + if (SubsamplingScaleImageViewStateHelper.sHeight(this) > reqHeight || SubsamplingScaleImageViewStateHelper.sWidth(this) > reqWidth) { // Use helper // Calculate ratios of height and width to requested height and width - final int heightRatio = Math.round((float) sHeight() / (float) reqHeight); - final int widthRatio = Math.round((float) sWidth() / (float) reqWidth); + final int heightRatio = Math.round((float) SubsamplingScaleImageViewStateHelper.sHeight(this) / (float) reqHeight); // Use helper + final int widthRatio = Math.round((float) SubsamplingScaleImageViewStateHelper.sWidth(this) / (float) reqWidth); // Use helper // Choose the smallest ratio as inSampleSize value, this will guarantee // a final image with both dimensions larger than or equal to the @@ -1151,7 +1154,7 @@ public void fitToBounds(boolean center) { scale = satTemp.scale; vTranslate.set(satTemp.vTranslate); if (init && minimumScaleType != SCALE_TYPE_START) { - vTranslate.set(vTranslateForSCenter(sWidth() / 2.0f, sHeight() / 2.0f, scale)); + vTranslate.set(SubsamplingScaleImageViewStateHelper.vTranslateForSCenter(this, SubsamplingScaleImageViewStateHelper.sWidth(this) / 2.0f, SubsamplingScaleImageViewStateHelper.sHeight(this) / 2.0f, scale)); // Use helper } } @@ -1165,22 +1168,22 @@ private void initialiseTileMap(Point maxTileDimensions) { int xTiles = 1; int yTiles = 1; while (true) { - int sTileWidth = sWidth() / xTiles; - int sTileHeight = sHeight() / yTiles; + int sTileWidth = SubsamplingScaleImageViewStateHelper.sWidth(this) / xTiles; + int sTileHeight = SubsamplingScaleImageViewStateHelper.sHeight(this) / yTiles; int subTileWidth = sTileWidth / sampleSize; int subTileHeight = sTileHeight / sampleSize; while (subTileWidth + xTiles + 1 > maxTileDimensions.x || (subTileWidth > getWidth() * 1.25 && sampleSize < fullImageSampleSize)) { xTiles += 1; - sTileWidth = sWidth() / xTiles; + sTileWidth = SubsamplingScaleImageViewStateHelper.sWidth(this) / xTiles; subTileWidth = sTileWidth / sampleSize; } while (subTileHeight + yTiles + 1 > maxTileDimensions.y || (subTileHeight > getHeight() * 1.25 && sampleSize < fullImageSampleSize)) { yTiles += 1; - sTileHeight = sHeight() / yTiles; + sTileHeight = SubsamplingScaleImageViewStateHelper.sHeight(this) / yTiles; subTileHeight = sTileHeight / sampleSize; } @@ -1195,8 +1198,8 @@ private void initialiseTileMap(Point maxTileDimensions) { new Rect( x * sTileWidth, y * sTileHeight, - x == xTiles - 1 ? sWidth() : (x + 1) * sTileWidth, - y == yTiles - 1 ? sHeight() : (y + 1) * sTileHeight); + x == xTiles - 1 ? SubsamplingScaleImageViewStateHelper.sWidth(this) : (x + 1) * sTileWidth, + y == yTiles - 1 ? SubsamplingScaleImageViewStateHelper.sHeight(this) : (y + 1) * sTileHeight); tile.vRect = new Rect(0, 0, 0, 0); tile.fileSRect = new Rect(tile.sRect); tileGrid.add(tile); @@ -1280,7 +1283,8 @@ protected Bitmap doInBackground(Void... params) { try { if (decoder.isReady()) { // Update tile's file sRect according to rotation - view.fileSRect(tile.sRect, tile.fileSRect); + // Use helper method (view is SubsamplingScaleImageView instance) + SubsamplingScaleImageViewStateHelper.fileSRect(view, tile.sRect, tile.fileSRect); if (view.sRegion != null) { tile.fileSRect.offset(view.sRegion.left, view.sRegion.top); } @@ -1559,7 +1563,8 @@ public static class Anim { } public static class ScaleAndTranslate { - ScaleAndTranslate(float scale, PointF vTranslate) { + // Make constructor public so helper class can access it + public ScaleAndTranslate(float scale, PointF vTranslate) { this.scale = scale; this.vTranslate = vTranslate; } @@ -1612,64 +1617,6 @@ public Point getMaxBitmapDimensions(Canvas canvas) { return new Point(Math.min(canvas.getMaximumBitmapWidth(), maxTileWidth), Math.min(canvas.getMaximumBitmapHeight(), maxTileHeight)); } - /** Get source width taking rotation into account. */ - @SuppressWarnings("SuspiciousNameCombination") - public int sWidth() { - int rotation = getRequiredRotation(); - - if (rotation == 90 || rotation == 270) { - return sHeight; - } else { - return sWidth; - } - } - - /** Get source height taking rotation into account. */ - @SuppressWarnings("SuspiciousNameCombination") - public int sHeight() { - int rotation = getRequiredRotation(); - - if (rotation == 90 || rotation == 270) { - return sWidth; - } else { - return sHeight; - } - } - - /** - * Converts source rectangle from tile, which treats the image file as if it were in the correct - * orientation already, to the rectangle of the image that needs to be loaded. - */ - @SuppressWarnings("SuspiciousNameCombination") - @AnyThread - public void fileSRect(Rect sRect, Rect target) { - if (getRequiredRotation() == 0) { - target.set(sRect); - } else if (getRequiredRotation() == 90) { - target.set(sRect.top, sHeight - sRect.right, sRect.bottom, sHeight - sRect.left); - } else if (getRequiredRotation() == 180) { - target.set( - sWidth - sRect.right, - sHeight - sRect.bottom, - sWidth - sRect.left, - sHeight - sRect.top); - } else { - target.set(sWidth - sRect.bottom, sRect.left, sWidth - sRect.top, sRect.right); - } - } - - /** - * Determines the rotation to be applied to tiles, based on EXIF orientation or chosen setting. - */ - @AnyThread - public int getRequiredRotation() { - if (orientation == ORIENTATION_USE_EXIF) { - return sOrientation; - } else { - return orientation; - } - } - /** Pythagoras distance between two points. */ private float distance(float x0, float x1, float y0, float y1) { float x = x0 - x1; @@ -1691,255 +1638,8 @@ public void recycle() { tileBgPaint = null; } - /** Convert screen to source x coordinate. */ - public float viewToSourceX(float vx) { - if (vTranslate == null) { - return Float.NaN; - } - - return (vx - vTranslate.x) / scale; - } - - /** Convert screen to source y coordinate. */ - public float viewToSourceY(float vy) { - if (vTranslate == null) { - return Float.NaN; - } - - return (vy - vTranslate.y) / scale; - } - - /** - * Converts a rectangle within the view to the corresponding rectangle from the source file, - * taking into account the current scale, translation, orientation and clipped region. This can - * be used to decode a bitmap from the source file. - * - *

This method will only work when the image has fully initialised, after {@link #isReady()} - * returns true. It is not guaranteed to work with preloaded bitmaps. - * - *

The result is written to the fRect argument. Re-use a single instance for efficiency. - * - * @param vRect rectangle representing the view area to interpret. - * @param fRect rectangle instance to which the result will be written. Re-use for efficiency. - */ - public void viewToFileRect(Rect vRect, Rect fRect) { - if (vTranslate == null || !readySent) { - return; - } - fRect.set((int) viewToSourceX(vRect.left), (int) viewToSourceY(vRect.top), (int) viewToSourceX(vRect.right), (int) viewToSourceY(vRect.bottom)); - fileSRect(fRect, fRect); - fRect.set(Math.max(0, fRect.left), Math.max(0, fRect.top), Math.min(sWidth, fRect.right), Math.min(sHeight, fRect.bottom)); - if (sRegion != null) { - fRect.offset(sRegion.left, sRegion.top); - } - } - - /** - * Find the area of the source file that is currently visible on screen, taking into account the - * current scale, translation, orientation and clipped region. This is a convenience method; see - * {@link #viewToFileRect(Rect, Rect)}. - * - * @param fRect rectangle instance to which the result will be written. Re-use for efficiency. - */ - public void visibleFileRect(Rect fRect) { - if (vTranslate == null || !readySent) { - return; - } - - fRect.set(0, 0, getWidth(), getHeight()); - viewToFileRect(fRect, fRect); - } - - /** - * Convert screen coordinate to source coordinate. - * - * @param vxy view X/Y coordinate. - * @return a coordinate representing the corresponding source coordinate. - */ - @Nullable - public final PointF viewToSourceCoord(PointF vxy) { - return viewToSourceCoord(vxy.x, vxy.y, new PointF()); - } - - /** - * Convert screen coordinate to source coordinate. - * - * @param vx view X coordinate. - * @param vy view Y coordinate. - * @return a coordinate representing the corresponding source coordinate. - */ - @Nullable - public final PointF viewToSourceCoord(float vx, float vy) { - return viewToSourceCoord(vx, vy, new PointF()); - } - - /** - * Convert screen coordinate to source coordinate. - * - * @param vxy view coordinates to convert. - * @param sTarget target object for result. The same instance is also returned. - * @return source coordinates. This is the same instance passed to the sTarget param. - */ - @Nullable - public final PointF viewToSourceCoord(PointF vxy, @NonNull PointF sTarget) { - return viewToSourceCoord(vxy.x, vxy.y, sTarget); - } - - /** - * Convert screen coordinate to source coordinate. - * - * @param vx view X coordinate. - * @param vy view Y coordinate. - * @param sTarget target object for result. The same instance is also returned. - * @return source coordinates. This is the same instance passed to the sTarget param. - */ - @Nullable - public final PointF viewToSourceCoord(float vx, float vy, @NonNull PointF sTarget) { - if (vTranslate == null) { - return null; - } - - sTarget.set(viewToSourceX(vx), viewToSourceY(vy)); - - return sTarget; - } - - /** Convert source to view x coordinate. */ - public float sourceToViewX(float sx) { - if (vTranslate == null) { - return Float.NaN; - } - - return (sx * scale) + vTranslate.x; - } - - /** Convert source to view y coordinate. */ - public float sourceToViewY(float sy) { - if (vTranslate == null) { - return Float.NaN; - } - - return (sy * scale) + vTranslate.y; - } - - /** - * Convert source coordinate to view coordinate. - * - * @param sxy source coordinates to convert. - * @return view coordinates. - */ - @Nullable - public final PointF sourceToViewCoord(PointF sxy) { - return sourceToViewCoord(sxy.x, sxy.y, new PointF()); - } - - /** - * Convert source coordinate to view coordinate. - * - * @param sx source X coordinate. - * @param sy source Y coordinate. - * @return view coordinates. - */ - @Nullable - public final PointF sourceToViewCoord(float sx, float sy) { - return sourceToViewCoord(sx, sy, new PointF()); - } - - /** - * Convert source coordinate to view coordinate. - * - * @param sxy source coordinates to convert. - * @param vTarget target object for result. The same instance is also returned. - * @return view coordinates. This is the same instance passed to the vTarget param. - */ - @SuppressWarnings("UnusedReturnValue") - @Nullable - public final PointF sourceToViewCoord(PointF sxy, @NonNull PointF vTarget) { - return sourceToViewCoord(sxy.x, sxy.y, vTarget); - } - - /** - * Convert source coordinate to view coordinate. - * - * @param sx source X coordinate. - * @param sy source Y coordinate. - * @param vTarget target object for result. The same instance is also returned. - * @return view coordinates. This is the same instance passed to the vTarget param. - */ - @Nullable - public final PointF sourceToViewCoord(float sx, float sy, @NonNull PointF vTarget) { - if (vTranslate == null) { - return null; - } - - vTarget.set(sourceToViewX(sx), sourceToViewY(sy)); - return vTarget; - } - - /** Convert source rect to screen rect, integer values. */ - public void sourceToViewRect(@NonNull Rect sRect, @NonNull Rect vTarget) { - vTarget.set((int) sourceToViewX(sRect.left), (int) sourceToViewY(sRect.top), (int) sourceToViewX(sRect.right), (int) sourceToViewY(sRect.bottom)); - } - - /** - * Get the translation required to place a given source coordinate at the center of the screen, - * with the center adjusted for asymmetric padding. Accepts the desired scale as an argument, so - * this is independent of current translate and scale. The result is fitted to bounds, putting - * the image point as near to the screen center as permitted. - */ - @NonNull - public PointF vTranslateForSCenter(float sCenterX, float sCenterY, float scale) { - int vxCenter = getPaddingLeft() + (getWidth() - getPaddingRight() - getPaddingLeft()) / 2; - int vyCenter = getPaddingTop() + (getHeight() - getPaddingBottom() - getPaddingTop()) / 2; - - if (satTemp == null) { - satTemp = new ScaleAndTranslate(0, new PointF(0, 0)); - } - - satTemp.scale = scale; - satTemp.vTranslate.set(vxCenter - (sCenterX * scale), vyCenter - (sCenterY * scale)); - SubsamplingScaleImageViewDrawHelper.fitToBounds(this, true, satTemp); - - return satTemp.vTranslate; - } - - /** - * Given a requested source center and scale, calculate what the actual center will have to be - * to keep the image in pan limits, keeping the requested center as near to the middle of the - * screen as allowed. - */ - @NonNull - PointF limitedSCenter( - float sCenterX, float sCenterY, float scale, @NonNull PointF sTarget) { - PointF vTranslate = vTranslateForSCenter(sCenterX, sCenterY, scale); - int vxCenter = getPaddingLeft() + (getWidth() - getPaddingRight() - getPaddingLeft()) / 2; - int vyCenter = getPaddingTop() + (getHeight() - getPaddingBottom() - getPaddingTop()) / 2; - float sx = (vxCenter - vTranslate.x) / scale; - float sy = (vyCenter - vTranslate.y) / scale; - sTarget.set(sx, sy); - return sTarget; - } - - /** Returns the minimum allowed scale. */ - public float minScale() { - int vPadding = getPaddingBottom() + getPaddingTop(); - int hPadding = getPaddingLeft() + getPaddingRight(); - if (minimumScaleType == SCALE_TYPE_CENTER_CROP || minimumScaleType == SCALE_TYPE_START) { - return Math.max((getWidth() - hPadding) / (float) sWidth(), (getHeight() - vPadding) / (float) sHeight()); - } else if (minimumScaleType == SCALE_TYPE_CUSTOM && minScale > 0) { - return minScale; - } else { - return Math.min((getWidth() - hPadding) / (float) sWidth(), (getHeight() - vPadding) / (float) sHeight()); - } - } - - /** Adjust a requested scale to be within the allowed limits. */ - public float limitedScale(float targetScale) { - targetScale = Math.max(minScale(), targetScale); - targetScale = Math.min(maxScale, targetScale); - - return targetScale; - } + // Methods related to coordinate transformations, state, and derived properties + // have been moved to SubsamplingScaleImageViewStateHelper.java /** * Apply a selected type of easing. @@ -2085,8 +1785,8 @@ public final void getPanRemaining(RectF vTarget) { return; } - float scaleWidth = scale * sWidth(); - float scaleHeight = scale * sHeight(); + float scaleWidth = scale * SubsamplingScaleImageViewStateHelper.sWidth(this); + float scaleHeight = scale * SubsamplingScaleImageViewStateHelper.sHeight(this); if (panLimit == PAN_LIMIT_CENTER) { vTarget.top = Math.max(0, -(vTranslate.y - (getHeight() / 2.0f))); @@ -2207,7 +1907,7 @@ public float getMaxScale() { * @return the minimum scale as a source/view pixels ratio. */ public final float getMinScale() { - return minScale(); + return SubsamplingScaleImageViewStateHelper.minScale(this); // Use helper } /** @@ -2238,9 +1938,7 @@ public void setMinimumTileDpi(int minimumTileDpi) { */ @Nullable public final PointF getCenter() { - int mX = getWidth() / 2; - int mY = getHeight() / 2; - return viewToSourceCoord(mX, mY); + return SubsamplingScaleImageViewStateHelper.getCenter(this); // Use helper } /** @@ -2273,10 +1971,10 @@ public final void setScaleAndCenter(float scale, @Nullable PointF sCenter) { */ public final void resetScaleAndCenter() { this.anim = null; - this.pendingScale = limitedScale(0); + this.pendingScale = SubsamplingScaleImageViewStateHelper.limitedScale(this, 0); if (isReady()) { - this.sPendingCenter = new PointF(sWidth() / 2.0f, sHeight() / 2.0f); + this.sPendingCenter = new PointF(SubsamplingScaleImageViewStateHelper.sWidth(this) / 2.0f, SubsamplingScaleImageViewStateHelper.sHeight(this) / 2.0f); } else { this.sPendingCenter = new PointF(0, 0); } @@ -2338,6 +2036,16 @@ public final int getSHeight() { return sHeight; } + // Getter for the private minScale field, needed by helper + public float getMinScaleField() { + return minScale; + } + + // Getter for minimumScaleType, needed by helper + public int getMinimumScaleType() { + return minimumScaleType; + } + /** * Returns the orientation setting. This can return {@link #ORIENTATION_USE_EXIF}, in which case * it doesn't tell you the applied orientation of the image. For that, use {@link @@ -2357,7 +2065,7 @@ public final int getOrientation() { * @return the orientation applied after EXIF information has been extracted. See static fields. */ public final int getAppliedOrientation() { - return getRequiredRotation(); + return SubsamplingScaleImageViewStateHelper.getRequiredRotation(this); // Use helper } /** @@ -2371,7 +2079,7 @@ public final int getAppliedOrientation() { public final ImageViewState getState() { if (vTranslate != null && sWidth > 0 && sHeight > 0) { // noinspection ConstantConditions - return new ImageViewState(getScale(), getCenter(), getOrientation()); + return new ImageViewState(getScale(), SubsamplingScaleImageViewStateHelper.getCenter(this), getOrientation()); // Use helper for getCenter() } return null; } @@ -2431,8 +2139,8 @@ public final void setPanEnabled(boolean panEnabled) { this.panEnabled = panEnabled; if (!panEnabled && vTranslate != null) { - vTranslate.x = (getWidth() / 2.0f) - (scale * (sWidth() / 2.0f)); - vTranslate.y = (getHeight() / 2.0f) - (scale * (sHeight() / 2.0f)); + vTranslate.x = (getWidth() / 2.0f) - (scale * (SubsamplingScaleImageViewStateHelper.sWidth(this) / 2.0f)); + vTranslate.y = (getHeight() / 2.0f) - (scale * (SubsamplingScaleImageViewStateHelper.sHeight(this) / 2.0f)); if (isReady()) { refreshRequiredTiles(true); @@ -2595,7 +2303,7 @@ public void sendStateChanged(float oldScale, PointF oldVTranslate, int origin) { } if (onStateChangedListener != null && !vTranslate.equals(oldVTranslate)) { - onStateChangedListener.onCenterChanged(getCenter(), origin); + onStateChangedListener.onCenterChanged(SubsamplingScaleImageViewStateHelper.getCenter(this), origin); // Use helper } } diff --git a/app/src/main/java/me/edgan/redditslide/util/ImageViewGestureListener.java b/app/src/main/java/me/edgan/redditslide/util/ImageViewGestureListener.java index 4f9ef070f..8543c447a 100644 --- a/app/src/main/java/me/edgan/redditslide/util/ImageViewGestureListener.java +++ b/app/src/main/java/me/edgan/redditslide/util/ImageViewGestureListener.java @@ -64,7 +64,7 @@ public boolean onDoubleTap(MotionEvent e) { view.isQuickScaling = true; view.isZooming = true; view.quickScaleLastDistance = -1F; - view.quickScaleSCenter = view.viewToSourceCoord(view.vCenterStart); + view.quickScaleSCenter = SubsamplingScaleImageViewStateHelper.viewToSourceCoord(view, view.vCenterStart); // Use helper view.quickScaleVStart = new PointF(e.getX(), e.getY()); view.quickScaleVLastPoint = new PointF(view.quickScaleSCenter.x, view.quickScaleSCenter.y); view.quickScaleMoved = false; @@ -73,7 +73,7 @@ public boolean onDoubleTap(MotionEvent e) { return false; } else { // Start double tap zoom animation. - view.doubleTapZoom(view.viewToSourceCoord(new PointF(e.getX(), e.getY())), new PointF(e.getX(), e.getY())); + view.doubleTapZoom(SubsamplingScaleImageViewStateHelper.viewToSourceCoord(view, new PointF(e.getX(), e.getY())), new PointF(e.getX(), e.getY())); // Use helper return true; } diff --git a/app/src/main/java/me/edgan/redditslide/util/SubsamplingScaleImageViewDrawHelper.java b/app/src/main/java/me/edgan/redditslide/util/SubsamplingScaleImageViewDrawHelper.java index 14af93274..eddbfff1b 100644 --- a/app/src/main/java/me/edgan/redditslide/util/SubsamplingScaleImageViewDrawHelper.java +++ b/app/src/main/java/me/edgan/redditslide/util/SubsamplingScaleImageViewDrawHelper.java @@ -62,8 +62,8 @@ public static void drawContent(SubsamplingScaleImageView view, Canvas canvas) { float vFocusNowY = view.ease(view.anim.easing, scaleElapsed, view.anim.vFocusStart.y, view.anim.vFocusEnd.y - view.anim.vFocusStart.y, view.anim.duration); // Find out where the focal point is at this scale and adjust its position to follow the animation path - view.vTranslate.x -= view.sourceToViewX(view.anim.sCenterEnd.x) - vFocusNowX; - view.vTranslate.y -= view.sourceToViewY(view.anim.sCenterEnd.y) - vFocusNowY; + view.vTranslate.x -= SubsamplingScaleImageViewStateHelper.sourceToViewX(view, view.anim.sCenterEnd.x) - vFocusNowX; // Use helper + view.vTranslate.y -= SubsamplingScaleImageViewStateHelper.sourceToViewY(view, view.anim.sCenterEnd.y) - vFocusNowY; // Use helper // For translate anims, showing the image non-centered is never allowed, for scaling anims it is during the animation. view.fitToBounds(finished || (view.anim.scaleStart == view.anim.scaleEnd)); @@ -108,7 +108,7 @@ public static void drawContent(SubsamplingScaleImageView view, Canvas canvas) { for (Map.Entry> tileMapEntry : view.tileMap.entrySet()) { if (tileMapEntry.getKey() == sampleSize || hasMissingTiles) { for (SubsamplingScaleImageView.Tile tile : tileMapEntry.getValue()) { - view.sourceToViewRect(tile.sRect, tile.vRect); + SubsamplingScaleImageViewStateHelper.sourceToViewRect(view, tile.sRect, tile.vRect); // Use helper if (!tile.loading && tile.bitmap != null) { if (view.tileBgPaint != null) { canvas.drawRect(tile.vRect, view.tileBgPaint); @@ -130,7 +130,7 @@ public static void drawContent(SubsamplingScaleImageView view, Canvas canvas) { 0, tile.bitmap.getHeight()); - if (view.getRequiredRotation() == SubsamplingScaleImageView.ORIENTATION_0) { + if (SubsamplingScaleImageViewStateHelper.getRequiredRotation(view) == SubsamplingScaleImageView.ORIENTATION_0) { // Use helper setMatrixArray( view.dstArray, tile.vRect.left, @@ -141,7 +141,7 @@ public static void drawContent(SubsamplingScaleImageView view, Canvas canvas) { tile.vRect.bottom, tile.vRect.left, tile.vRect.bottom); - } else if (view.getRequiredRotation() == SubsamplingScaleImageView.ORIENTATION_90) { + } else if (SubsamplingScaleImageViewStateHelper.getRequiredRotation(view) == SubsamplingScaleImageView.ORIENTATION_90) { // Use helper setMatrixArray( view.dstArray, tile.vRect.right, @@ -152,7 +152,7 @@ public static void drawContent(SubsamplingScaleImageView view, Canvas canvas) { tile.vRect.bottom, tile.vRect.left, tile.vRect.top); - } else if (view.getRequiredRotation() == SubsamplingScaleImageView.ORIENTATION_180) { + } else if (SubsamplingScaleImageViewStateHelper.getRequiredRotation(view) == SubsamplingScaleImageView.ORIENTATION_180) { // Use helper setMatrixArray( view.dstArray, tile.vRect.right, @@ -163,7 +163,7 @@ public static void drawContent(SubsamplingScaleImageView view, Canvas canvas) { tile.vRect.top, tile.vRect.right, tile.vRect.top); - } else if (view.getRequiredRotation() == SubsamplingScaleImageView.ORIENTATION_270) { + } else if (SubsamplingScaleImageViewStateHelper.getRequiredRotation(view) == SubsamplingScaleImageView.ORIENTATION_270) { // Use helper setMatrixArray( view.dstArray, tile.vRect.left, @@ -224,14 +224,14 @@ public static void drawContent(SubsamplingScaleImageView view, Canvas canvas) { view.matrix.reset(); view.matrix.postScale(xScale, yScale); - view.matrix.postRotate(view.getRequiredRotation()); + view.matrix.postRotate(SubsamplingScaleImageViewStateHelper.getRequiredRotation(view)); // Use helper view.matrix.postTranslate(view.vTranslate.x, view.vTranslate.y); - if (view.getRequiredRotation() == SubsamplingScaleImageView.ORIENTATION_180) { + if (SubsamplingScaleImageViewStateHelper.getRequiredRotation(view) == SubsamplingScaleImageView.ORIENTATION_180) { // Use helper view.matrix.postTranslate(view.scale * view.sWidth, view.scale * view.sHeight); - } else if (view.getRequiredRotation() == SubsamplingScaleImageView.ORIENTATION_90) { + } else if (SubsamplingScaleImageViewStateHelper.getRequiredRotation(view) == SubsamplingScaleImageView.ORIENTATION_90) { // Use helper view.matrix.postTranslate(view.scale * view.sHeight, 0); - } else if (view.getRequiredRotation() == SubsamplingScaleImageView.ORIENTATION_270) { + } else if (SubsamplingScaleImageViewStateHelper.getRequiredRotation(view) == SubsamplingScaleImageView.ORIENTATION_270) { // Use helper view.matrix.postTranslate(0, view.scale * view.sWidth); } @@ -255,7 +255,7 @@ public static void drawContent(SubsamplingScaleImageView view, Canvas canvas) { "Scale: " + String.format(Locale.ENGLISH, "%.2f", view.scale) + " (" - + String.format(Locale.ENGLISH, "%.2f", view.minScale()) + + String.format(Locale.ENGLISH, "%.2f", SubsamplingScaleImageViewStateHelper.minScale(view)) // Use helper + " - " + String.format(Locale.ENGLISH, "%.2f", view.maxScale) + ")", @@ -281,9 +281,9 @@ public static void drawContent(SubsamplingScaleImageView view, Canvas canvas) { px(view, 45), view.debugTextPaint); if (view.anim != null) { - PointF vCenterStart = view.sourceToViewCoord(view.anim.sCenterStart); - PointF vCenterEndRequested = view.sourceToViewCoord(view.anim.sCenterEndRequested); - PointF vCenterEnd = view.sourceToViewCoord(view.anim.sCenterEnd); + PointF vCenterStart = SubsamplingScaleImageViewStateHelper.sourceToViewCoord(view, view.anim.sCenterStart); // Use helper + PointF vCenterEndRequested = SubsamplingScaleImageViewStateHelper.sourceToViewCoord(view, view.anim.sCenterEndRequested); // Use helper + PointF vCenterEnd = SubsamplingScaleImageViewStateHelper.sourceToViewCoord(view, view.anim.sCenterEnd); // Use helper // noinspection ConstantConditions canvas.drawCircle(vCenterStart.x, vCenterStart.y, px(view, 10), view.debugLinePaint); view.debugLinePaint.setColor(Color.RED); @@ -302,7 +302,7 @@ public static void drawContent(SubsamplingScaleImageView view, Canvas canvas) { } if (view.quickScaleSCenter != null) { view.debugLinePaint.setColor(Color.BLUE); - canvas.drawCircle(view.sourceToViewX(view.quickScaleSCenter.x), view.sourceToViewY(view.quickScaleSCenter.y), px(view, 35), view.debugLinePaint); + canvas.drawCircle(SubsamplingScaleImageViewStateHelper.sourceToViewX(view, view.quickScaleSCenter.x), SubsamplingScaleImageViewStateHelper.sourceToViewY(view, view.quickScaleSCenter.y), px(view, 35), view.debugLinePaint); // Use helper } if (view.quickScaleVStart != null && view.isQuickScaling) { view.debugLinePaint.setColor(Color.CYAN); @@ -357,9 +357,9 @@ public static void fitToBounds(SubsamplingScaleImageView view, boolean center, S PointF vTranslate = sat.vTranslate; // Use view's limitedScale method - ensure it's accessible (made public in next step) - float scale = view.limitedScale(sat.scale); - float scaleWidth = scale * view.sWidth(); - float scaleHeight = scale * view.sHeight(); + float scale = SubsamplingScaleImageViewStateHelper.limitedScale(view, sat.scale); // Use helper + float scaleWidth = scale * SubsamplingScaleImageViewStateHelper.sWidth(view); // Use helper + float scaleHeight = scale * SubsamplingScaleImageViewStateHelper.sHeight(view); // Use helper if (view.panLimit == SubsamplingScaleImageView.PAN_LIMIT_CENTER && view.isReady()) { vTranslate.x = Math.max(vTranslate.x, view.getWidth() / 2.0f - scaleWidth); diff --git a/app/src/main/java/me/edgan/redditslide/util/SubsamplingScaleImageViewStateHelper.java b/app/src/main/java/me/edgan/redditslide/util/SubsamplingScaleImageViewStateHelper.java new file mode 100644 index 000000000..841d63675 --- /dev/null +++ b/app/src/main/java/me/edgan/redditslide/util/SubsamplingScaleImageViewStateHelper.java @@ -0,0 +1,341 @@ +package me.edgan.redditslide.util; + +import android.graphics.PointF; +import android.graphics.Rect; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +// Needed for getState logic if moved here (kept in main class for now) + +import me.edgan.redditslide.Views.SubsamplingScaleImageView; +import me.edgan.redditslide.Views.SubsamplingScaleImageView.ScaleAndTranslate; // Need access to inner class + +// Import necessary classes used by the moved methods +import static me.edgan.redditslide.Views.SubsamplingScaleImageView.*; // For constants like ORIENTATION_USE_EXIF, SCALE_TYPE_*, PAN_LIMIT_* + +/** + * Helper class containing static methods extracted from SubsamplingScaleImageView + * for managing state, coordinate transformations, and calculating derived properties. + */ +public class SubsamplingScaleImageViewStateHelper { + + /** Convert screen to source x coordinate. */ + public static float viewToSourceX(SubsamplingScaleImageView view, float vx) { + if (view.vTranslate == null) { + return Float.NaN; + } + return (vx - view.vTranslate.x) / view.scale; + } + + /** Convert screen to source y coordinate. */ + public static float viewToSourceY(SubsamplingScaleImageView view, float vy) { + if (view.vTranslate == null) { + return Float.NaN; + } + return (vy - view.vTranslate.y) / view.scale; + } + + /** + * Converts a rectangle within the view to the corresponding rectangle from the source file, + * taking into account the current scale, translation, orientation and clipped region. This can + * be used to decode a bitmap from the source file. + * + *

This method will only work when the image has fully initialised, after {@link SubsamplingScaleImageView#isReady()} + * returns true. It is not guaranteed to work with preloaded bitmaps. + * + *

The result is written to the fRect argument. Re-use a single instance for efficiency. + * + * @param vRect rectangle representing the view area to interpret. + * @param fRect rectangle instance to which the result will be written. Re-use for efficiency. + */ + public static void viewToFileRect(SubsamplingScaleImageView view, Rect vRect, Rect fRect) { + if (view.vTranslate == null || !view.isReady()) { // Use isReady() getter + return; + } + fRect.set((int) viewToSourceX(view, vRect.left), (int) viewToSourceY(view, vRect.top), (int) viewToSourceX(view, vRect.right), (int) viewToSourceY(view, vRect.bottom)); + fileSRect(view, fRect, fRect); // Call the helper version + fRect.set(Math.max(0, fRect.left), Math.max(0, fRect.top), Math.min(view.getSWidth(), fRect.right), Math.min(view.getSHeight(), fRect.bottom)); // Use getters for sWidth/sHeight + if (view.sRegion != null) { // Assume sRegion is accessible + fRect.offset(view.sRegion.left, view.sRegion.top); + } + } + + /** + * Find the area of the source file that is currently visible on screen, taking into account the + * current scale, translation, orientation and clipped region. This is a convenience method; see + * {@link #viewToFileRect(SubsamplingScaleImageView, Rect, Rect)}. + * + * @param fRect rectangle instance to which the result will be written. Re-use for efficiency. + */ + public static void visibleFileRect(SubsamplingScaleImageView view, Rect fRect) { + if (view.vTranslate == null || !view.isReady()) { // Use isReady() getter + return; + } + fRect.set(0, 0, view.getWidth(), view.getHeight()); + viewToFileRect(view, fRect, fRect); + } + + + /** + * Convert screen coordinate to source coordinate. + * + * @param vxy view X/Y coordinate. + * @return a coordinate representing the corresponding source coordinate. + */ + @Nullable + public static PointF viewToSourceCoord(SubsamplingScaleImageView view, PointF vxy) { + return viewToSourceCoord(view, vxy.x, vxy.y, new PointF()); + } + + /** + * Convert screen coordinate to source coordinate. + * + * @param vx view X coordinate. + * @param vy view Y coordinate. + * @return a coordinate representing the corresponding source coordinate. + */ + @Nullable + public static PointF viewToSourceCoord(SubsamplingScaleImageView view, float vx, float vy) { + return viewToSourceCoord(view, vx, vy, new PointF()); + } + + /** + * Convert screen coordinate to source coordinate. + * + * @param vxy view coordinates to convert. + * @param sTarget target object for result. The same instance is also returned. + * @return source coordinates. This is the same instance passed to the sTarget param. + */ + @Nullable + public static PointF viewToSourceCoord(SubsamplingScaleImageView view, PointF vxy, @NonNull PointF sTarget) { + return viewToSourceCoord(view, vxy.x, vxy.y, sTarget); + } + + /** + * Convert screen coordinate to source coordinate. + * + * @param vx view X coordinate. + * @param vy view Y coordinate. + * @param sTarget target object for result. The same instance is also returned. + * @return source coordinates. This is the same instance passed to the sTarget param. + */ + @Nullable + public static PointF viewToSourceCoord(SubsamplingScaleImageView view, float vx, float vy, @NonNull PointF sTarget) { + if (view.vTranslate == null) { + return null; + } + sTarget.set(viewToSourceX(view, vx), viewToSourceY(view, vy)); + return sTarget; + } + + /** Convert source to view x coordinate. */ + public static float sourceToViewX(SubsamplingScaleImageView view, float sx) { + if (view.vTranslate == null) { + return Float.NaN; + } + return (sx * view.scale) + view.vTranslate.x; + } + + /** Convert source to view y coordinate. */ + public static float sourceToViewY(SubsamplingScaleImageView view, float sy) { + if (view.vTranslate == null) { + return Float.NaN; + } + return (sy * view.scale) + view.vTranslate.y; + } + + /** + * Convert source coordinate to view coordinate. + * + * @param sxy source coordinates to convert. + * @return view coordinates. + */ + @Nullable + public static PointF sourceToViewCoord(SubsamplingScaleImageView view, PointF sxy) { + return sourceToViewCoord(view, sxy.x, sxy.y, new PointF()); + } + + /** + * Convert source coordinate to view coordinate. + * + * @param sx source X coordinate. + * @param sy source Y coordinate. + * @return view coordinates. + */ + @Nullable + public static PointF sourceToViewCoord(SubsamplingScaleImageView view, float sx, float sy) { + return sourceToViewCoord(view, sx, sy, new PointF()); + } + + /** + * Convert source coordinate to view coordinate. + * + * @param sxy source coordinates to convert. + * @param vTarget target object for result. The same instance is also returned. + * @return view coordinates. This is the same instance passed to the vTarget param. + */ + @SuppressWarnings("UnusedReturnValue") + @Nullable + public static PointF sourceToViewCoord(SubsamplingScaleImageView view, PointF sxy, @NonNull PointF vTarget) { + return sourceToViewCoord(view, sxy.x, sxy.y, vTarget); + } + + /** + * Convert source coordinate to view coordinate. + * + * @param sx source X coordinate. + * @param sy source Y coordinate. + * @param vTarget target object for result. The same instance is also returned. + * @return view coordinates. This is the same instance passed to the vTarget param. + */ + @Nullable + public static PointF sourceToViewCoord(SubsamplingScaleImageView view, float sx, float sy, @NonNull PointF vTarget) { + if (view.vTranslate == null) { + return null; + } + vTarget.set(sourceToViewX(view, sx), sourceToViewY(view, sy)); + return vTarget; + } + + /** Convert source rect to screen rect, integer values. */ + public static void sourceToViewRect(SubsamplingScaleImageView view, @NonNull Rect sRect, @NonNull Rect vTarget) { + vTarget.set((int) sourceToViewX(view, sRect.left), (int) sourceToViewY(view, sRect.top), (int) sourceToViewX(view, sRect.right), (int) sourceToViewY(view, sRect.bottom)); + } + + /** + * Get the translation required to place a given source coordinate at the center of the screen, + * with the center adjusted for asymmetric padding. Accepts the desired scale as an argument, so + * this is independent of current translate and scale. The result is fitted to bounds, putting + * the image point as near to the screen center as permitted. + */ + @NonNull + public static PointF vTranslateForSCenter(SubsamplingScaleImageView view, float sCenterX, float sCenterY, float scale) { + int vxCenter = view.getPaddingLeft() + (view.getWidth() - view.getPaddingRight() - view.getPaddingLeft()) / 2; + int vyCenter = view.getPaddingTop() + (view.getHeight() - view.getPaddingBottom() - view.getPaddingTop()) / 2; + + // satTemp is volatile in the original class, used for temporary calculations. + // Recreating it here for the calculation should be fine. + ScaleAndTranslate satTemp = new ScaleAndTranslate(0, new PointF(0, 0)); + + satTemp.scale = scale; + satTemp.vTranslate.set(vxCenter - (sCenterX * scale), vyCenter - (sCenterY * scale)); + // Call the existing helper in the util package + SubsamplingScaleImageViewDrawHelper.fitToBounds(view, true, satTemp); + + return satTemp.vTranslate; + } + + /** + * Given a requested source center and scale, calculate what the actual center will have to be + * to keep the image in pan limits, keeping the requested center as near to the middle of the + * screen as allowed. + */ + @NonNull + public static PointF limitedSCenter(SubsamplingScaleImageView view, float sCenterX, float sCenterY, float scale, @NonNull PointF sTarget) { + PointF vTranslate = vTranslateForSCenter(view, sCenterX, sCenterY, scale); // Call helper version + int vxCenter = view.getPaddingLeft() + (view.getWidth() - view.getPaddingRight() - view.getPaddingLeft()) / 2; + int vyCenter = view.getPaddingTop() + (view.getHeight() - view.getPaddingBottom() - view.getPaddingTop()) / 2; + float sx = (vxCenter - vTranslate.x) / scale; + float sy = (vyCenter - vTranslate.y) / scale; + sTarget.set(sx, sy); + return sTarget; + } + + /** Returns the minimum allowed scale. */ + public static float minScale(SubsamplingScaleImageView view) { + int vPadding = view.getPaddingBottom() + view.getPaddingTop(); + int hPadding = view.getPaddingLeft() + view.getPaddingRight(); + // Access fields via getters if possible, or assume direct access for now + int minimumScaleType = view.getMinimumScaleType(); + float minScaleField = view.getMinScaleField(); + + if (minimumScaleType == SCALE_TYPE_CENTER_CROP || minimumScaleType == SCALE_TYPE_START) { + // Use helper methods for sWidth/sHeight + return Math.max((view.getWidth() - hPadding) / (float) sWidth(view), (view.getHeight() - vPadding) / (float) sHeight(view)); + } else if (minimumScaleType == SCALE_TYPE_CUSTOM && minScaleField > 0) { + return minScaleField; + } else { // Default: SCALE_TYPE_CENTER_INSIDE + // Use helper methods for sWidth/sHeight + return Math.min((view.getWidth() - hPadding) / (float) sWidth(view), (view.getHeight() - vPadding) / (float) sHeight(view)); + } + } + + /** Adjust a requested scale to be within the allowed limits. */ + public static float limitedScale(SubsamplingScaleImageView view, float targetScale) { + targetScale = Math.max(minScale(view), targetScale); + targetScale = Math.min(view.getMaxScale(), targetScale); + return targetScale; + } + + /** Get source width taking rotation into account. */ + @SuppressWarnings("SuspiciousNameCombination") + public static int sWidth(SubsamplingScaleImageView view) { + int rotation = getRequiredRotation(view); + if (rotation == 90 || rotation == 270) { + return view.getSHeight(); + } else { + return view.getSWidth(); + } + } + + /** Get source height taking rotation into account. */ + @SuppressWarnings("SuspiciousNameCombination") + public static int sHeight(SubsamplingScaleImageView view) { + int rotation = getRequiredRotation(view); + if (rotation == 90 || rotation == 270) { + return view.getSWidth(); + } else { + return view.getSHeight(); + } + } + + /** + * Converts source rectangle from tile, which treats the image file as if it were in the correct + * orientation already, to the rectangle of the image that needs to be loaded. + */ + @SuppressWarnings("SuspiciousNameCombination") + public static void fileSRect(SubsamplingScaleImageView view, Rect sRect, Rect target) { + int requiredRotation = getRequiredRotation(view); + int sWidth = view.getSWidth(); + int sHeight = view.getSHeight(); + + if (requiredRotation == 0) { + target.set(sRect); + } else if (requiredRotation == 90) { + target.set(sRect.top, sHeight - sRect.right, sRect.bottom, sHeight - sRect.left); + } else if (requiredRotation == 180) { + target.set( + sWidth - sRect.right, + sHeight - sRect.bottom, + sWidth - sRect.left, + sHeight - sRect.top); + } else { // 270 + target.set(sWidth - sRect.bottom, sRect.left, sWidth - sRect.top, sRect.right); + } + } + + /** + * Determines the rotation to be applied to tiles, based on EXIF orientation or chosen setting. + */ + public static int getRequiredRotation(SubsamplingScaleImageView view) { + // Access fields via getters + int orientation = view.getOrientation(); + if (orientation == ORIENTATION_USE_EXIF) { + return view.sOrientation; // Assume sOrientation is accessible + } else { + return orientation; + } + } + + /** + * Returns the source point at the center of the view. + * + * @return the source coordinates current at the center of the view. + */ + @Nullable + public static PointF getCenter(SubsamplingScaleImageView view) { + int mX = view.getWidth() / 2; + int mY = view.getHeight() / 2; + return viewToSourceCoord(view, mX, mY); // Call helper version + } +} \ No newline at end of file diff --git a/app/src/main/java/me/edgan/redditslide/util/TouchEventUtil.java b/app/src/main/java/me/edgan/redditslide/util/TouchEventUtil.java index 3f4c6fc36..3be68166c 100644 --- a/app/src/main/java/me/edgan/redditslide/util/TouchEventUtil.java +++ b/app/src/main/java/me/edgan/redditslide/util/TouchEventUtil.java @@ -75,10 +75,10 @@ public static boolean handleTouchEventInternal(@NonNull SubsamplingScaleImageVie double previousScale = view.scale; view.scale = Math.min(view.maxScale, (vDistEnd / view.vDistStart) * view.scaleStart); - if (view.scale <= view.minScale()) { + if (view.scale <= SubsamplingScaleImageViewStateHelper.minScale(view)) { // Use helper // Minimum scale reached so don't pan. Adjust start settings so any expand will zoom in. view.vDistStart = vDistEnd; - view.scaleStart = view.minScale(); + view.scaleStart = SubsamplingScaleImageViewStateHelper.minScale(view); // Use helper view.vCenterStart.set(vCenterEndX, vCenterEndY); view.vTranslateStart.set(view.vTranslate); } else if (view.panEnabled) { @@ -89,10 +89,10 @@ public static boolean handleTouchEventInternal(@NonNull SubsamplingScaleImageVie float vTopNow = vTopStart * (view.scale / view.scaleStart); view.vTranslate.x = vCenterEndX - vLeftNow; view.vTranslate.y = vCenterEndY - vTopNow; - if ((previousScale * view.sHeight() < view.getHeight() - && view.scale * view.sHeight() >= view.getHeight()) - || (previousScale * view.sWidth() < view.getWidth() - && view.scale * view.sWidth() >= view.getWidth())) { + if ((previousScale * SubsamplingScaleImageViewStateHelper.sHeight(view) < view.getHeight() // Use helper + && view.scale * SubsamplingScaleImageViewStateHelper.sHeight(view) >= view.getHeight()) // Use helper + || (previousScale * SubsamplingScaleImageViewStateHelper.sWidth(view) < view.getWidth() // Use helper + && view.scale * SubsamplingScaleImageViewStateHelper.sWidth(view) >= view.getWidth())) { // Use helper view.fitToBounds(true); view.vCenterStart.set(vCenterEndX, vCenterEndY); view.vTranslateStart.set(view.vTranslate); @@ -105,8 +105,8 @@ public static boolean handleTouchEventInternal(@NonNull SubsamplingScaleImageVie view.vTranslate.y = (view.getHeight() / 2.0f) - (view.scale * view.sRequestedCenter.y); } else { // With no requested center, scale around the image center. - view.vTranslate.x = (view.getWidth() / 2.0f) - (view.scale * (view.sWidth() / 2.0f)); - view.vTranslate.y = (view.getHeight() / 2.0f) - (view.scale * (view.sHeight() / 2.0f)); + view.vTranslate.x = (view.getWidth() / 2.0f) - (view.scale * (SubsamplingScaleImageViewStateHelper.sWidth(view) / 2.0f)); // Use helper + view.vTranslate.y = (view.getHeight() / 2.0f) - (view.scale * (SubsamplingScaleImageViewStateHelper.sHeight(view) / 2.0f)); // Use helper } view.fitToBounds(true); @@ -136,7 +136,7 @@ public static boolean handleTouchEventInternal(@NonNull SubsamplingScaleImageVie } double previousScale = view.scale; - view.scale = Math.max(view.minScale(), Math.min(view.maxScale, view.scale * multiplier)); + view.scale = Math.max(SubsamplingScaleImageViewStateHelper.minScale(view), Math.min(view.maxScale, view.scale * multiplier)); // Use helper if (view.panEnabled) { float vLeftStart = view.vCenterStart.x - view.vTranslateStart.x; @@ -146,12 +146,12 @@ public static boolean handleTouchEventInternal(@NonNull SubsamplingScaleImageVie view.vTranslate.x = view.vCenterStart.x - vLeftNow; view.vTranslate.y = view.vCenterStart.y - vTopNow; - if ((previousScale * view.sHeight() < view.getHeight() - && view.scale * view.sHeight() >= view.getHeight()) - || (previousScale * view.sWidth() < view.getWidth() - && view.scale * view.sWidth() >= view.getWidth())) { + if ((previousScale * SubsamplingScaleImageViewStateHelper.sHeight(view) < view.getHeight() // Use helper + && view.scale * SubsamplingScaleImageViewStateHelper.sHeight(view) >= view.getHeight()) // Use helper + || (previousScale * SubsamplingScaleImageViewStateHelper.sWidth(view) < view.getWidth() // Use helper + && view.scale * SubsamplingScaleImageViewStateHelper.sWidth(view) >= view.getWidth())) { // Use helper view.fitToBounds(true); - view.vCenterStart.set(view.sourceToViewCoord(view.quickScaleSCenter)); + view.vCenterStart.set(SubsamplingScaleImageViewStateHelper.sourceToViewCoord(view, view.quickScaleSCenter)); // Use helper view.vTranslateStart.set(view.vTranslate); view.scaleStart = view.scale; dist = 0; @@ -162,8 +162,8 @@ public static boolean handleTouchEventInternal(@NonNull SubsamplingScaleImageVie view.vTranslate.y = (view.getHeight() / 2.0f) - (view.scale * view.sRequestedCenter.y); } else { // With no requested center, scale around the image center. - view.vTranslate.x = (view.getWidth() / 2.0f) - (view.scale * (view.sWidth() / 2.0f)); - view.vTranslate.y = (view.getHeight() / 2.0f) - (view.scale * (view.sHeight() / 2.0f)); + view.vTranslate.x = (view.getWidth() / 2.0f) - (view.scale * (SubsamplingScaleImageViewStateHelper.sWidth(view) / 2.0f)); // Use helper + view.vTranslate.y = (view.getHeight() / 2.0f) - (view.scale * (SubsamplingScaleImageViewStateHelper.sHeight(view) / 2.0f)); // Use helper } } From 5b712e2ef62e628285e51139ea22f0570e7ef95a Mon Sep 17 00:00:00 2001 From: Nathan Grennan Date: Tue, 1 Apr 2025 22:26:34 -0700 Subject: [PATCH 22/99] Broke TileManager.java out of SubsamplingScaleImageView.java --- .../Views/SubsamplingScaleImageView.java | 113 +------------ .../edgan/redditslide/util/TileManager.java | 149 ++++++++++++++++++ 2 files changed, 156 insertions(+), 106 deletions(-) create mode 100644 app/src/main/java/me/edgan/redditslide/util/TileManager.java diff --git a/app/src/main/java/me/edgan/redditslide/Views/SubsamplingScaleImageView.java b/app/src/main/java/me/edgan/redditslide/Views/SubsamplingScaleImageView.java index bd26d4596..cbc4367ac 100644 --- a/app/src/main/java/me/edgan/redditslide/Views/SubsamplingScaleImageView.java +++ b/app/src/main/java/me/edgan/redditslide/Views/SubsamplingScaleImageView.java @@ -49,15 +49,14 @@ import me.edgan.redditslide.util.TouchEventUtil; import java.lang.ref.WeakReference; -import java.util.ArrayList; import java.util.Arrays; -import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.concurrent.Executor; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import me.edgan.redditslide.util.SubsamplingScaleImageViewStateHelper; // Added import +import me.edgan.redditslide.util.TileManager; // Added import import me.edgan.redditslide.util.TilesInitTask; /** @@ -999,59 +998,12 @@ public synchronized void initialiseBaseLayer(@NonNull Point maxTileDimensions) { * performance. */ public void refreshRequiredTiles(boolean load) { - if (decoder == null || tileMap == null) { - return; - } - - int sampleSize = Math.min(fullImageSampleSize, calculateInSampleSize(scale)); - - // Load tiles of the correct sample size that are on screen. Discard tiles off screen, and - // those that are higher - // resolution than required, or lower res than required but not the base layer, so the base - // layer is always present. - for (Map.Entry> tileMapEntry : tileMap.entrySet()) { - for (Tile tile : tileMapEntry.getValue()) { - if (tile.sampleSize < sampleSize - || (tile.sampleSize > sampleSize - && tile.sampleSize != fullImageSampleSize)) { - tile.visible = false; - if (tile.bitmap != null) { - tile.bitmap.recycle(); - tile.bitmap = null; - } - } - if (tile.sampleSize == sampleSize) { - if (tileVisible(tile)) { - tile.visible = true; - if (!tile.loading && tile.bitmap == null && load) { - TileLoadTask task = new TileLoadTask(this, decoder, tile); - execute(task); - } - } else if (tile.sampleSize != fullImageSampleSize) { - tile.visible = false; - if (tile.bitmap != null) { - tile.bitmap.recycle(); - tile.bitmap = null; - } - } - } else if (tile.sampleSize == fullImageSampleSize) { - tile.visible = true; - } - } - } + TileManager.refreshRequiredTiles(this, load); } /** Determine whether tile is visible. */ private boolean tileVisible(Tile tile) { - float sVisLeft = SubsamplingScaleImageViewStateHelper.viewToSourceX(this, 0), // Use helper - sVisRight = SubsamplingScaleImageViewStateHelper.viewToSourceX(this, getWidth()), // Use helper - sVisTop = SubsamplingScaleImageViewStateHelper.viewToSourceY(this, 0), // Use helper - sVisBottom = SubsamplingScaleImageViewStateHelper.viewToSourceY(this, getHeight()); // Use helper - - return !(sVisLeft > tile.sRect.right - || tile.sRect.left > sVisRight - || sVisTop > tile.sRect.bottom - || tile.sRect.top > sVisBottom); + return TileManager.tileVisible(this, tile); } /** Sets scale and translate ready for the next draw. */ @@ -1162,58 +1114,7 @@ public void fitToBounds(boolean center) { * Once source image and view dimensions are known, creates a map of sample size to tile grid. */ private void initialiseTileMap(Point maxTileDimensions) { - debug("initialiseTileMap maxTileDimensions=%dx%d", maxTileDimensions.x, maxTileDimensions.y); - this.tileMap = new LinkedHashMap<>(); - int sampleSize = fullImageSampleSize; - int xTiles = 1; - int yTiles = 1; - while (true) { - int sTileWidth = SubsamplingScaleImageViewStateHelper.sWidth(this) / xTiles; - int sTileHeight = SubsamplingScaleImageViewStateHelper.sHeight(this) / yTiles; - int subTileWidth = sTileWidth / sampleSize; - int subTileHeight = sTileHeight / sampleSize; - - while (subTileWidth + xTiles + 1 > maxTileDimensions.x - || (subTileWidth > getWidth() * 1.25 && sampleSize < fullImageSampleSize)) { - xTiles += 1; - sTileWidth = SubsamplingScaleImageViewStateHelper.sWidth(this) / xTiles; - subTileWidth = sTileWidth / sampleSize; - } - - while (subTileHeight + yTiles + 1 > maxTileDimensions.y - || (subTileHeight > getHeight() * 1.25 && sampleSize < fullImageSampleSize)) { - yTiles += 1; - sTileHeight = SubsamplingScaleImageViewStateHelper.sHeight(this) / yTiles; - subTileHeight = sTileHeight / sampleSize; - } - - List tileGrid = new ArrayList<>(xTiles * yTiles); - - for (int x = 0; x < xTiles; x++) { - for (int y = 0; y < yTiles; y++) { - Tile tile = new Tile(); - tile.sampleSize = sampleSize; - tile.visible = sampleSize == fullImageSampleSize; - tile.sRect = - new Rect( - x * sTileWidth, - y * sTileHeight, - x == xTiles - 1 ? SubsamplingScaleImageViewStateHelper.sWidth(this) : (x + 1) * sTileWidth, - y == yTiles - 1 ? SubsamplingScaleImageViewStateHelper.sHeight(this) : (y + 1) * sTileHeight); - tile.vRect = new Rect(0, 0, 0, 0); - tile.fileSRect = new Rect(tile.sRect); - tileGrid.add(tile); - } - } - - tileMap.put(sampleSize, tileGrid); - - if (sampleSize == 1) { - break; - } else { - sampleSize /= 2; - } - } + TileManager.initialiseTileMap(this, maxTileDimensions); } /** Async task used to get image details without blocking the UI thread. */ @@ -1258,13 +1159,13 @@ public synchronized void onTilesInited(ImageRegionDecoder decoder, int sWidth, i } /** Async task used to load images without blocking the UI thread. */ - private static class TileLoadTask extends AsyncTask { + public static class TileLoadTask extends AsyncTask { // Changed visibility to public private final WeakReference viewRef; private final WeakReference decoderRef; private final WeakReference tileRef; private Exception exception; - TileLoadTask(SubsamplingScaleImageView view, ImageRegionDecoder decoder, Tile tile) { + public TileLoadTask(SubsamplingScaleImageView view, ImageRegionDecoder decoder, Tile tile) { // Added public modifier this.viewRef = new WeakReference<>(view); this.decoderRef = new WeakReference<>(decoder); this.tileRef = new WeakReference<>(tile); @@ -1531,7 +1432,7 @@ public int getExifOrientation(Context context, String sourceUri) { return exifOrientation; } - private void execute(AsyncTask asyncTask) { + public void execute(AsyncTask asyncTask) { // Changed visibility to public asyncTask.executeOnExecutor(executor); } public static class Tile { diff --git a/app/src/main/java/me/edgan/redditslide/util/TileManager.java b/app/src/main/java/me/edgan/redditslide/util/TileManager.java new file mode 100644 index 000000000..5e1f537d5 --- /dev/null +++ b/app/src/main/java/me/edgan/redditslide/util/TileManager.java @@ -0,0 +1,149 @@ +package me.edgan.redditslide.util; + +import android.graphics.Point; +import android.graphics.Rect; +import me.edgan.redditslide.Views.SubsamplingScaleImageView; +import me.edgan.redditslide.Views.SubsamplingScaleImageView.Tile; // Assuming Tile is public or package-private and accessible + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * Helper class for managing tiles in SubsamplingScaleImageView. + */ +public class TileManager { + + /** + * Once source image and view dimensions are known, creates a map of sample size to tile grid. + */ + public static void initialiseTileMap(SubsamplingScaleImageView view, Point maxTileDimensions) { + view.debug("initialiseTileMap maxTileDimensions=%dx%d", maxTileDimensions.x, maxTileDimensions.y); + view.tileMap = new LinkedHashMap<>(); + int sampleSize = view.fullImageSampleSize; + int xTiles = 1; + int yTiles = 1; + while (true) { + int sTileWidth = SubsamplingScaleImageViewStateHelper.sWidth(view) / xTiles; + int sTileHeight = SubsamplingScaleImageViewStateHelper.sHeight(view) / yTiles; + int subTileWidth = sTileWidth / sampleSize; + int subTileHeight = sTileHeight / sampleSize; + + while (subTileWidth + xTiles + 1 > maxTileDimensions.x + || (subTileWidth > view.getWidth() * 1.25 && sampleSize < view.fullImageSampleSize)) { + xTiles += 1; + sTileWidth = SubsamplingScaleImageViewStateHelper.sWidth(view) / xTiles; + subTileWidth = sTileWidth / sampleSize; + } + + while (subTileHeight + yTiles + 1 > maxTileDimensions.y + || (subTileHeight > view.getHeight() * 1.25 && sampleSize < view.fullImageSampleSize)) { + yTiles += 1; + sTileHeight = SubsamplingScaleImageViewStateHelper.sHeight(view) / yTiles; + subTileHeight = sTileHeight / sampleSize; + } + + List tileGrid = new ArrayList<>(xTiles * yTiles); + + for (int x = 0; x < xTiles; x++) { + for (int y = 0; y < yTiles; y++) { + Tile tile = new Tile(); + tile.sampleSize = sampleSize; + tile.visible = sampleSize == view.fullImageSampleSize; + tile.sRect = + new Rect( + x * sTileWidth, + y * sTileHeight, + x == xTiles - 1 ? SubsamplingScaleImageViewStateHelper.sWidth(view) : (x + 1) * sTileWidth, + y == yTiles - 1 ? SubsamplingScaleImageViewStateHelper.sHeight(view) : (y + 1) * sTileHeight); + tile.vRect = new Rect(0, 0, 0, 0); + tile.fileSRect = new Rect(tile.sRect); + tileGrid.add(tile); + } + } + + view.tileMap.put(sampleSize, tileGrid); + + if (sampleSize == 1) { + break; + } else { + sampleSize /= 2; + } + } + } + + + /** + * Loads the optimum tiles for display at the current scale and translate, so the screen can be + * filled with tiles that are at least as high resolution as the screen. Frees up bitmaps that + * are now off the screen. + * + * @param view The SubsamplingScaleImageView instance. + * @param load Whether to load the new tiles needed. Use false while scrolling/panning for + * performance. + */ + public static void refreshRequiredTiles(SubsamplingScaleImageView view, boolean load) { + if (view.decoder == null || view.tileMap == null) { + return; + } + + int sampleSize = Math.min(view.fullImageSampleSize, view.calculateInSampleSize(view.scale)); + + // Load tiles of the correct sample size that are on screen. Discard tiles off screen, and + // those that are higher + // resolution than required, or lower res than required but not the base layer, so the base + // layer is always present. + for (Map.Entry> tileMapEntry : view.tileMap.entrySet()) { + for (Tile tile : tileMapEntry.getValue()) { + if (tile.sampleSize < sampleSize + || (tile.sampleSize > sampleSize + && tile.sampleSize != view.fullImageSampleSize)) { + tile.visible = false; + if (tile.bitmap != null) { + tile.bitmap.recycle(); + tile.bitmap = null; + } + } + if (tile.sampleSize == sampleSize) { + if (TileManager.tileVisible(view, tile)) { // Updated call + tile.visible = true; + if (!tile.loading && tile.bitmap == null && load) { + // Need access to TileLoadTask, assume it's accessible or adjust visibility + // For now, assuming view.execute can handle it or we adjust TileLoadTask later + SubsamplingScaleImageView.TileLoadTask task = new SubsamplingScaleImageView.TileLoadTask(view, view.decoder, tile); + view.execute(task); // Assuming execute is public or package-private + } + } else if (tile.sampleSize != view.fullImageSampleSize) { + tile.visible = false; + if (tile.bitmap != null) { + tile.bitmap.recycle(); + tile.bitmap = null; + } + } + } else if (tile.sampleSize == view.fullImageSampleSize) { + tile.visible = true; + } + } + } + } + + /** + * Determine whether tile is visible. + * @param view The SubsamplingScaleImageView instance. + * @param tile The tile to check. + * @return true if the tile is visible. + * */ + public static boolean tileVisible(SubsamplingScaleImageView view, Tile tile) { + float sVisLeft = SubsamplingScaleImageViewStateHelper.viewToSourceX(view, 0), // Use helper + sVisRight = SubsamplingScaleImageViewStateHelper.viewToSourceX(view, view.getWidth()), // Use helper + sVisTop = SubsamplingScaleImageViewStateHelper.viewToSourceY(view, 0), // Use helper + sVisBottom = SubsamplingScaleImageViewStateHelper.viewToSourceY(view, view.getHeight()); // Use helper + + return !(sVisLeft > tile.sRect.right + || tile.sRect.left > sVisRight + || sVisTop > tile.sRect.bottom + || tile.sRect.top > sVisBottom); + } + +} \ No newline at end of file From 4fbe99ef11b5618ef141dabadca2b7e510ff8813 Mon Sep 17 00:00:00 2001 From: Nathan Grennan Date: Tue, 1 Apr 2025 22:35:11 -0700 Subject: [PATCH 23/99] Clean up of SubsamplingScaleImageView.java and TileManager.java --- .../Views/SubsamplingScaleImageView.java | 60 +++++++++---------- .../edgan/redditslide/util/TileManager.java | 16 +++-- 2 files changed, 35 insertions(+), 41 deletions(-) diff --git a/app/src/main/java/me/edgan/redditslide/Views/SubsamplingScaleImageView.java b/app/src/main/java/me/edgan/redditslide/Views/SubsamplingScaleImageView.java index cbc4367ac..bcb1d0111 100644 --- a/app/src/main/java/me/edgan/redditslide/Views/SubsamplingScaleImageView.java +++ b/app/src/main/java/me/edgan/redditslide/Views/SubsamplingScaleImageView.java @@ -711,7 +711,7 @@ public boolean onSingleTapConfirmed(MotionEvent e) { @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { debug("onSizeChanged %dx%d -> %dx%d", oldw, oldh, w, h); - PointF sCenter = SubsamplingScaleImageViewStateHelper.getCenter(this); // Use helper + PointF sCenter = SubsamplingScaleImageViewStateHelper.getCenter(this); if (readySent && sCenter != null) { this.anim = null; this.pendingScale = scale; @@ -737,12 +737,12 @@ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { if (sWidth > 0 && sHeight > 0) { if (resizeWidth && resizeHeight) { - width = SubsamplingScaleImageViewStateHelper.sWidth(this); // Use helper - height = SubsamplingScaleImageViewStateHelper.sHeight(this); // Use helper + width = SubsamplingScaleImageViewStateHelper.sWidth(this); + height = SubsamplingScaleImageViewStateHelper.sHeight(this); } else if (resizeHeight) { - height = (int) ((((double) SubsamplingScaleImageViewStateHelper.sHeight(this) / (double) SubsamplingScaleImageViewStateHelper.sWidth(this)) * width)); // Use helper + height = (int) ((((double) SubsamplingScaleImageViewStateHelper.sHeight(this) / (double) SubsamplingScaleImageViewStateHelper.sWidth(this)) * width)); } else if (resizeWidth) { - width = (int) ((((double) SubsamplingScaleImageViewStateHelper.sWidth(this) / (double) SubsamplingScaleImageViewStateHelper.sHeight(this)) * height)); // Use helper + width = (int) ((((double) SubsamplingScaleImageViewStateHelper.sWidth(this) / (double) SubsamplingScaleImageViewStateHelper.sHeight(this)) * height)); } } @@ -825,14 +825,14 @@ public void doubleTapZoom(PointF sCenter, PointF vFocus) { sCenter.y = sRequestedCenter.y; } else { // With no requested center, scale around the image center. - sCenter.x = SubsamplingScaleImageViewStateHelper.sWidth(this) / 2.0f; // Use helper - sCenter.y = SubsamplingScaleImageViewStateHelper.sHeight(this) / 2.0f; // Use helper + sCenter.x = SubsamplingScaleImageViewStateHelper.sWidth(this) / 2.0f; + sCenter.y = SubsamplingScaleImageViewStateHelper.sHeight(this) / 2.0f; } } float doubleTapZoomScale = Math.min(maxScale, this.doubleTapZoomScale); - boolean zoomIn = (scale <= doubleTapZoomScale * 0.9) || scale == SubsamplingScaleImageViewStateHelper.minScale(this); // Use helper - float targetScale = zoomIn ? doubleTapZoomScale : SubsamplingScaleImageViewStateHelper.minScale(this); // Use helper + boolean zoomIn = (scale <= doubleTapZoomScale * 0.9) || scale == SubsamplingScaleImageViewStateHelper.minScale(this); + float targetScale = zoomIn ? doubleTapZoomScale : SubsamplingScaleImageViewStateHelper.minScale(this); if (doubleTapZoomStyle == ZOOM_FOCUS_CENTER_IMMEDIATE) { setScaleAndCenter(targetScale, sCenter); @@ -958,18 +958,17 @@ public synchronized void initialiseBaseLayer(@NonNull Point maxTileDimensions) { SubsamplingScaleImageViewDrawHelper.fitToBounds(this, true, satTemp); // Load double resolution - next level will be split into four tiles and at the center all - // four are required, - // so don't bother with tiling until the next level 16 tiles are needed. + // four are required, so don't bother with tiling until the next level 16 tiles are needed. fullImageSampleSize = calculateInSampleSize(satTemp.scale); if (fullImageSampleSize > 1) { fullImageSampleSize /= 2; } - if (fullImageSampleSize == 1 && sRegion == null && SubsamplingScaleImageViewStateHelper.sWidth(this) < maxTileDimensions.x && SubsamplingScaleImageViewStateHelper.sHeight(this) < maxTileDimensions.y) { // Use helper + if (fullImageSampleSize == 1 && sRegion == null && SubsamplingScaleImageViewStateHelper.sWidth(this) < maxTileDimensions.x + && SubsamplingScaleImageViewStateHelper.sHeight(this) < maxTileDimensions.y) { // Whole image is required at native resolution, and is smaller than the canvas max - // bitmap size. - // Use BitmapDecoder for better image support. + // bitmap size. Use BitmapDecoder for better image support. decoder.recycle(); decoder = null; BitmapLoadTask task = @@ -1039,8 +1038,8 @@ public int calculateInSampleSize(float scale) { scale = (minimumTileDpi / averageDpi) * scale; } - int reqWidth = (int) (SubsamplingScaleImageViewStateHelper.sWidth(this) * scale); // Use helper - int reqHeight = (int) (SubsamplingScaleImageViewStateHelper.sHeight(this) * scale); // Use helper + int reqWidth = (int) (SubsamplingScaleImageViewStateHelper.sWidth(this) * scale); + int reqHeight = (int) (SubsamplingScaleImageViewStateHelper.sHeight(this) * scale); // Raw height and width of image int inSampleSize = 1; @@ -1048,12 +1047,11 @@ public int calculateInSampleSize(float scale) { return 32; } - if (SubsamplingScaleImageViewStateHelper.sHeight(this) > reqHeight || SubsamplingScaleImageViewStateHelper.sWidth(this) > reqWidth) { // Use helper + if (SubsamplingScaleImageViewStateHelper.sHeight(this) > reqHeight || SubsamplingScaleImageViewStateHelper.sWidth(this) > reqWidth) { // Calculate ratios of height and width to requested height and width - final int heightRatio = Math.round((float) SubsamplingScaleImageViewStateHelper.sHeight(this) / (float) reqHeight); // Use helper - final int widthRatio = Math.round((float) SubsamplingScaleImageViewStateHelper.sWidth(this) / (float) reqWidth); // Use helper - + final int heightRatio = Math.round((float) SubsamplingScaleImageViewStateHelper.sHeight(this) / (float) reqHeight); + final int widthRatio = Math.round((float) SubsamplingScaleImageViewStateHelper.sWidth(this) / (float) reqWidth); // Choose the smallest ratio as inSampleSize value, this will guarantee // a final image with both dimensions larger than or equal to the // requested height and width. @@ -1106,7 +1104,8 @@ public void fitToBounds(boolean center) { scale = satTemp.scale; vTranslate.set(satTemp.vTranslate); if (init && minimumScaleType != SCALE_TYPE_START) { - vTranslate.set(SubsamplingScaleImageViewStateHelper.vTranslateForSCenter(this, SubsamplingScaleImageViewStateHelper.sWidth(this) / 2.0f, SubsamplingScaleImageViewStateHelper.sHeight(this) / 2.0f, scale)); // Use helper + vTranslate.set(SubsamplingScaleImageViewStateHelper.vTranslateForSCenter( + this, SubsamplingScaleImageViewStateHelper.sWidth(this) / 2.0f, SubsamplingScaleImageViewStateHelper.sHeight(this) / 2.0f, scale)); } } @@ -1159,13 +1158,13 @@ public synchronized void onTilesInited(ImageRegionDecoder decoder, int sWidth, i } /** Async task used to load images without blocking the UI thread. */ - public static class TileLoadTask extends AsyncTask { // Changed visibility to public + public static class TileLoadTask extends AsyncTask { private final WeakReference viewRef; private final WeakReference decoderRef; private final WeakReference tileRef; private Exception exception; - public TileLoadTask(SubsamplingScaleImageView view, ImageRegionDecoder decoder, Tile tile) { // Added public modifier + public TileLoadTask(SubsamplingScaleImageView view, ImageRegionDecoder decoder, Tile tile) { this.viewRef = new WeakReference<>(view); this.decoderRef = new WeakReference<>(decoder); this.tileRef = new WeakReference<>(tile); @@ -1432,7 +1431,7 @@ public int getExifOrientation(Context context, String sourceUri) { return exifOrientation; } - public void execute(AsyncTask asyncTask) { // Changed visibility to public + public void execute(AsyncTask asyncTask) { asyncTask.executeOnExecutor(executor); } public static class Tile { @@ -1539,9 +1538,6 @@ public void recycle() { tileBgPaint = null; } - // Methods related to coordinate transformations, state, and derived properties - // have been moved to SubsamplingScaleImageViewStateHelper.java - /** * Apply a selected type of easing. * @@ -1808,7 +1804,7 @@ public float getMaxScale() { * @return the minimum scale as a source/view pixels ratio. */ public final float getMinScale() { - return SubsamplingScaleImageViewStateHelper.minScale(this); // Use helper + return SubsamplingScaleImageViewStateHelper.minScale(this); } /** @@ -1839,7 +1835,7 @@ public void setMinimumTileDpi(int minimumTileDpi) { */ @Nullable public final PointF getCenter() { - return SubsamplingScaleImageViewStateHelper.getCenter(this); // Use helper + return SubsamplingScaleImageViewStateHelper.getCenter(this); } /** @@ -1966,7 +1962,7 @@ public final int getOrientation() { * @return the orientation applied after EXIF information has been extracted. See static fields. */ public final int getAppliedOrientation() { - return SubsamplingScaleImageViewStateHelper.getRequiredRotation(this); // Use helper + return SubsamplingScaleImageViewStateHelper.getRequiredRotation(this); } /** @@ -1980,7 +1976,7 @@ public final int getAppliedOrientation() { public final ImageViewState getState() { if (vTranslate != null && sWidth > 0 && sHeight > 0) { // noinspection ConstantConditions - return new ImageViewState(getScale(), SubsamplingScaleImageViewStateHelper.getCenter(this), getOrientation()); // Use helper for getCenter() + return new ImageViewState(getScale(), SubsamplingScaleImageViewStateHelper.getCenter(this), getOrientation()); } return null; } @@ -2204,7 +2200,7 @@ public void sendStateChanged(float oldScale, PointF oldVTranslate, int origin) { } if (onStateChangedListener != null && !vTranslate.equals(oldVTranslate)) { - onStateChangedListener.onCenterChanged(SubsamplingScaleImageViewStateHelper.getCenter(this), origin); // Use helper + onStateChangedListener.onCenterChanged(SubsamplingScaleImageViewStateHelper.getCenter(this), origin); } } diff --git a/app/src/main/java/me/edgan/redditslide/util/TileManager.java b/app/src/main/java/me/edgan/redditslide/util/TileManager.java index 5e1f537d5..19ba58bb4 100644 --- a/app/src/main/java/me/edgan/redditslide/util/TileManager.java +++ b/app/src/main/java/me/edgan/redditslide/util/TileManager.java @@ -3,7 +3,7 @@ import android.graphics.Point; import android.graphics.Rect; import me.edgan.redditslide.Views.SubsamplingScaleImageView; -import me.edgan.redditslide.Views.SubsamplingScaleImageView.Tile; // Assuming Tile is public or package-private and accessible +import me.edgan.redditslide.Views.SubsamplingScaleImageView.Tile; import java.util.ArrayList; import java.util.LinkedHashMap; @@ -90,10 +90,8 @@ public static void refreshRequiredTiles(SubsamplingScaleImageView view, boolean int sampleSize = Math.min(view.fullImageSampleSize, view.calculateInSampleSize(view.scale)); - // Load tiles of the correct sample size that are on screen. Discard tiles off screen, and - // those that are higher - // resolution than required, or lower res than required but not the base layer, so the base - // layer is always present. + // Load tiles of the correct sample size that are on screen. Discard tiles off screen, and those that are higher + // resolution than required, or lower res than required but not the base layer, so the base layer is always present. for (Map.Entry> tileMapEntry : view.tileMap.entrySet()) { for (Tile tile : tileMapEntry.getValue()) { if (tile.sampleSize < sampleSize @@ -135,10 +133,10 @@ public static void refreshRequiredTiles(SubsamplingScaleImageView view, boolean * @return true if the tile is visible. * */ public static boolean tileVisible(SubsamplingScaleImageView view, Tile tile) { - float sVisLeft = SubsamplingScaleImageViewStateHelper.viewToSourceX(view, 0), // Use helper - sVisRight = SubsamplingScaleImageViewStateHelper.viewToSourceX(view, view.getWidth()), // Use helper - sVisTop = SubsamplingScaleImageViewStateHelper.viewToSourceY(view, 0), // Use helper - sVisBottom = SubsamplingScaleImageViewStateHelper.viewToSourceY(view, view.getHeight()); // Use helper + float sVisLeft = SubsamplingScaleImageViewStateHelper.viewToSourceX(view, 0), + sVisRight = SubsamplingScaleImageViewStateHelper.viewToSourceX(view, view.getWidth()), + sVisTop = SubsamplingScaleImageViewStateHelper.viewToSourceY(view, 0), + sVisBottom = SubsamplingScaleImageViewStateHelper.viewToSourceY(view, view.getHeight()); return !(sVisLeft > tile.sRect.right || tile.sRect.left > sVisRight From 25ebe5bc94df57a8baddfd559920c229c95c610e Mon Sep 17 00:00:00 2001 From: Nathan Grennan Date: Wed, 2 Apr 2025 01:24:17 -0700 Subject: [PATCH 24/99] Broke SubsamplingScaleImageViewLoader.java out of SubsamplingScaleImageView.java --- .../redditslide/Activities/AlbumPager.java | 2 +- .../redditslide/Activities/MediaView.java | 6 +- .../redditslide/Activities/TumblrPager.java | 2 +- .../redditslide/Fragments/MediaFragment.java | 6 +- .../Fragments/MediaFragmentComment.java | 6 +- .../redditslide/Views/AnimationBuilder.java | 2 +- .../edgan/redditslide/Views/ImageSource.java | 20 +- .../redditslide/Views/PeekMediaView.java | 6 +- .../Views/SubsamplingScaleImageView.java | 581 ++---------------- .../util/ImageViewGestureListener.java | 6 +- .../SubsamplingScaleImageViewDrawHelper.java | 44 +- .../util/SubsamplingScaleImageViewLoader.java | 518 ++++++++++++++++ .../edgan/redditslide/util/TilesInitTask.java | 10 +- .../redditslide/util/TouchEventUtil.java | 32 +- 14 files changed, 650 insertions(+), 591 deletions(-) create mode 100644 app/src/main/java/me/edgan/redditslide/util/SubsamplingScaleImageViewLoader.java diff --git a/app/src/main/java/me/edgan/redditslide/Activities/AlbumPager.java b/app/src/main/java/me/edgan/redditslide/Activities/AlbumPager.java index 1318c594a..68835c738 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/AlbumPager.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/AlbumPager.java @@ -919,7 +919,7 @@ public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) { } size.setVisibility(View.GONE); - image.setImage(ImageSource.bitmap(loadedImage)); + image.loader.setImage(ImageSource.bitmap(loadedImage)); rootView.findViewById(R.id.progress).setVisibility(View.GONE); } }); diff --git a/app/src/main/java/me/edgan/redditslide/Activities/MediaView.java b/app/src/main/java/me/edgan/redditslide/Activities/MediaView.java index 0286eb841..6a8162be3 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/MediaView.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/MediaView.java @@ -941,7 +941,7 @@ public void onImageLoadError(Exception e) { } }); try { - i.setImage(ImageSource.uri(f.getAbsolutePath())); + i.loader.setImage(ImageSource.uri(f.getAbsolutePath())); } catch (Exception e) { imageShown = false; } @@ -1054,9 +1054,9 @@ public void onLoadingComplete( .getDiskCache() .get(url); if (f != null && f.exists()) { - i.setImage(ImageSource.uri(f.getAbsolutePath())); + i.loader.setImage(ImageSource.uri(f.getAbsolutePath())); } else { - i.setImage(ImageSource.bitmap(loadedImage)); + i.loader.setImage(ImageSource.bitmap(loadedImage)); } (findViewById(R.id.progress)).setVisibility(View.GONE); handler.removeCallbacks(progressBarDelayRunner); diff --git a/app/src/main/java/me/edgan/redditslide/Activities/TumblrPager.java b/app/src/main/java/me/edgan/redditslide/Activities/TumblrPager.java index 3237064f3..391a1886b 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/TumblrPager.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/TumblrPager.java @@ -712,7 +712,7 @@ public void onLoadingFailed( public void onLoadingComplete( String imageUri, View view, Bitmap loadedImage) { size.setVisibility(View.GONE); - image.setImage(ImageSource.bitmap(loadedImage)); + image.loader.setImage(ImageSource.bitmap(loadedImage)); (rootView.findViewById(R.id.progress)).setVisibility(View.GONE); } diff --git a/app/src/main/java/me/edgan/redditslide/Fragments/MediaFragment.java b/app/src/main/java/me/edgan/redditslide/Fragments/MediaFragment.java index 7ba3c81e5..24593cc0c 100644 --- a/app/src/main/java/me/edgan/redditslide/Fragments/MediaFragment.java +++ b/app/src/main/java/me/edgan/redditslide/Fragments/MediaFragment.java @@ -933,7 +933,7 @@ public void run() { imageShown = true; try { - i.setImage(ImageSource.uri(f.getAbsolutePath())); + i.loader.setImage(ImageSource.uri(f.getAbsolutePath())); } catch (Exception e) { // todo i.setImage(ImageSource.bitmap(loadedImage)); } @@ -1024,9 +1024,9 @@ public void onLoadingComplete( .get(url); } if (f != null && f.exists()) { - i.setImage(ImageSource.uri(f.getAbsolutePath())); + i.loader.setImage(ImageSource.uri(f.getAbsolutePath())); } else { - i.setImage(ImageSource.bitmap(loadedImage)); + i.loader.setImage(ImageSource.bitmap(loadedImage)); } (rootView.findViewById(R.id.progress)) .setVisibility(View.GONE); diff --git a/app/src/main/java/me/edgan/redditslide/Fragments/MediaFragmentComment.java b/app/src/main/java/me/edgan/redditslide/Fragments/MediaFragmentComment.java index d0b223484..5f082eebb 100644 --- a/app/src/main/java/me/edgan/redditslide/Fragments/MediaFragmentComment.java +++ b/app/src/main/java/me/edgan/redditslide/Fragments/MediaFragmentComment.java @@ -598,7 +598,7 @@ public void run() { imageShown = true; try { - i.setImage(ImageSource.uri(f.getAbsolutePath())); + i.loader.setImage(ImageSource.uri(f.getAbsolutePath())); } catch (Exception e) { // todo i.setImage(ImageSource.bitmap(loadedImage)); } @@ -691,9 +691,9 @@ public void onLoadingComplete( .get(url); } if (f != null && f.exists()) { - i.setImage(ImageSource.uri(f.getAbsolutePath())); + i.loader.setImage(ImageSource.uri(f.getAbsolutePath())); } else { - i.setImage(ImageSource.bitmap(loadedImage)); + i.loader.setImage(ImageSource.bitmap(loadedImage)); } (rootView.findViewById(R.id.progress)) .setVisibility(View.GONE); diff --git a/app/src/main/java/me/edgan/redditslide/Views/AnimationBuilder.java b/app/src/main/java/me/edgan/redditslide/Views/AnimationBuilder.java index fc94bef3e..86866d5b9 100644 --- a/app/src/main/java/me/edgan/redditslide/Views/AnimationBuilder.java +++ b/app/src/main/java/me/edgan/redditslide/Views/AnimationBuilder.java @@ -153,7 +153,7 @@ public void start() { int vxCenter = view.getPaddingLeft() + (view.getWidth() - view.getPaddingRight() - view.getPaddingLeft()) / 2; int vyCenter = view.getPaddingTop() + (view.getHeight() - view.getPaddingBottom() - view.getPaddingTop()) / 2; float targetScale = SubsamplingScaleImageViewStateHelper.limitedScale(view, this.targetScale); - PointF targetSCenter = panLimited ? SubsamplingScaleImageViewStateHelper.limitedSCenter(view, this.targetSCenter.x, this.targetSCenter.y, targetScale, new PointF()): this.targetSCenter; // Use helper + PointF targetSCenter = panLimited ? SubsamplingScaleImageViewStateHelper.limitedSCenter(view, this.targetSCenter.x, this.targetSCenter.y, targetScale, new PointF()): this.targetSCenter; view.anim = new SubsamplingScaleImageView.Anim(); view.anim.scaleStart = view.scale; view.anim.scaleEnd = targetScale; diff --git a/app/src/main/java/me/edgan/redditslide/Views/ImageSource.java b/app/src/main/java/me/edgan/redditslide/Views/ImageSource.java index 2b578ad01..5fdd1edc3 100644 --- a/app/src/main/java/me/edgan/redditslide/Views/ImageSource.java +++ b/app/src/main/java/me/edgan/redditslide/Views/ImageSource.java @@ -20,8 +20,8 @@ public final class ImageSource { - static final String FILE_SCHEME = "file:///"; - static final String ASSET_SCHEME = "file:///android_asset/"; + public static final String FILE_SCHEME = "file:///"; + public static final String ASSET_SCHEME = "file:///android_asset/"; private final Uri uri; private final Bitmap bitmap; @@ -216,35 +216,35 @@ private void setInvariants() { } } - protected final Uri getUri() { + public final Uri getUri() { return uri; } - protected final Bitmap getBitmap() { + public final Bitmap getBitmap() { return bitmap; } - protected final Integer getResource() { + public final Integer getResource() { return resource; } - protected final boolean getTile() { + public final boolean getTile() { return tile; } - protected final int getSWidth() { + public final int getSWidth() { return sWidth; } - protected final int getSHeight() { + public final int getSHeight() { return sHeight; } - protected final Rect getSRegion() { + public final Rect getSRegion() { return sRegion; } - protected final boolean isCached() { + public final boolean isCached() { return cached; } } diff --git a/app/src/main/java/me/edgan/redditslide/Views/PeekMediaView.java b/app/src/main/java/me/edgan/redditslide/Views/PeekMediaView.java index c6c25c6f4..86b0200b5 100644 --- a/app/src/main/java/me/edgan/redditslide/Views/PeekMediaView.java +++ b/app/src/main/java/me/edgan/redditslide/Views/PeekMediaView.java @@ -592,7 +592,7 @@ public void onImageLoadError(Exception e) { } }); try { - i.setImage(ImageSource.uri(f.getAbsolutePath())); + i.loader.setImage(ImageSource.uri(f.getAbsolutePath())); i.setZoomEnabled(false); } catch (Exception e) { imageShown = false; @@ -638,9 +638,9 @@ public void onLoadingComplete( .getDiskCache() .get(url); if (f != null && f.exists()) { - i.setImage(ImageSource.uri(f.getAbsolutePath())); + i.loader.setImage(ImageSource.uri(f.getAbsolutePath())); } else { - i.setImage(ImageSource.bitmap(loadedImage)); + i.loader.setImage(ImageSource.bitmap(loadedImage)); } (progress).setVisibility(View.GONE); handler.removeCallbacks(progressBarDelayRunner); diff --git a/app/src/main/java/me/edgan/redditslide/Views/SubsamplingScaleImageView.java b/app/src/main/java/me/edgan/redditslide/Views/SubsamplingScaleImageView.java index bcb1d0111..a29fd1594 100644 --- a/app/src/main/java/me/edgan/redditslide/Views/SubsamplingScaleImageView.java +++ b/app/src/main/java/me/edgan/redditslide/Views/SubsamplingScaleImageView.java @@ -1,9 +1,7 @@ package me.edgan.redditslide.Views; -import android.content.ContentResolver; import android.content.Context; import android.content.res.TypedArray; -import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; @@ -18,7 +16,6 @@ import android.os.AsyncTask; import android.os.Handler; import android.os.Message; -import android.provider.MediaStore; import android.util.AttributeSet; import android.util.DisplayMetrics; import android.util.Log; @@ -31,8 +28,6 @@ import androidx.annotation.AnyThread; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.exifinterface.media.ExifInterface; -import androidx.interpolator.view.animation.FastOutSlowInInterpolator; import com.davemorrissey.labs.subscaleview.ImageViewState; import com.davemorrissey.labs.subscaleview.R.styleable; @@ -44,7 +39,6 @@ import com.davemorrissey.labs.subscaleview.decoder.SkiaImageRegionDecoder; import me.edgan.redditslide.SettingValues; -import me.edgan.redditslide.util.ImageViewGestureListener; import me.edgan.redditslide.util.SubsamplingScaleImageViewDrawHelper; import me.edgan.redditslide.util.TouchEventUtil; @@ -55,9 +49,9 @@ import java.util.concurrent.Executor; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; -import me.edgan.redditslide.util.SubsamplingScaleImageViewStateHelper; // Added import -import me.edgan.redditslide.util.TileManager; // Added import -import me.edgan.redditslide.util.TilesInitTask; +import me.edgan.redditslide.util.SubsamplingScaleImageViewStateHelper; +import me.edgan.redditslide.util.TileManager; +import me.edgan.redditslide.util.SubsamplingScaleImageViewLoader; /** * Displays an image subsampled as necessary to avoid loading too much image data into memory. After @@ -80,7 +74,7 @@ @SuppressWarnings("unused") public class SubsamplingScaleImageView extends View { - private static final String TAG = SubsamplingScaleImageView.class.getSimpleName(); + public static final String TAG = SubsamplingScaleImageView.class.getSimpleName(); /** Attempt to use EXIF information on the image to rotate it. Works for external files only. */ public static final int ORIENTATION_USE_EXIF = -1; @@ -204,7 +198,7 @@ public class SubsamplingScaleImageView extends View { public boolean bitmapIsCached; // Uri of full size image - private Uri uri; + public Uri uri; // Sample size used to display the whole image when fully zoomed out public int fullImageSampleSize; @@ -235,8 +229,8 @@ public class SubsamplingScaleImageView extends View { // overrides for the dimensions of the generated tiles public static final int TILE_SIZE_AUTO = Integer.MAX_VALUE; - private int maxTileWidth = TILE_SIZE_AUTO; - private int maxTileHeight = TILE_SIZE_AUTO; + public int maxTileWidth = TILE_SIZE_AUTO; + public int maxTileHeight = TILE_SIZE_AUTO; // An executor service for loading of images private Executor executor = AsyncTask.THREAD_POOL_EXECUTOR; @@ -264,8 +258,8 @@ public class SubsamplingScaleImageView extends View { public PointF vTranslateBefore; // Source coordinate to center on, used when new position is set externally before view is ready - private Float pendingScale; - private PointF sPendingCenter; + public Float pendingScale; + public PointF sPendingCenter; public PointF sRequestedCenter; // Source image dimensions and orientation - dimensions relate to the unrotated image @@ -273,7 +267,7 @@ public class SubsamplingScaleImageView extends View { public int sHeight; public int sOrientation; public Rect sRegion; - private Rect pRegion; + public Rect pRegion; // Is two-finger zooming in progress public boolean isZooming; @@ -285,24 +279,16 @@ public class SubsamplingScaleImageView extends View { public int maxTouchCount; // Fling detector - private GestureDetector detector; - private GestureDetector singleDetector; + public GestureDetector detector; + public GestureDetector singleDetector; // Tile and image decoding public ImageRegionDecoder decoder; - private final ReadWriteLock decoderLock = new ReentrantReadWriteLock(true); - private DecoderFactory bitmapDecoderFactory = - new CompatDecoderFactory( - SkiaImageDecoder.class, - SettingValues.highColorspaceImages - ? Bitmap.Config.ARGB_8888 - : Bitmap.Config.RGB_565); - private DecoderFactory regionDecoderFactory = - new CompatDecoderFactory( - SkiaImageRegionDecoder.class, - SettingValues.highColorspaceImages - ? Bitmap.Config.ARGB_8888 - : Bitmap.Config.RGB_565); + public final ReadWriteLock decoderLock = new ReentrantReadWriteLock(true); + public DecoderFactory bitmapDecoderFactory = + new CompatDecoderFactory(SkiaImageDecoder.class, SettingValues.highColorspaceImages ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565); + public DecoderFactory regionDecoderFactory = + new CompatDecoderFactory(SkiaImageRegionDecoder.class, SettingValues.highColorspaceImages ? Bitmap.Config.ARGB_8888 : Bitmap.Config.RGB_565); // Debug values public PointF vCenterStart; @@ -345,7 +331,7 @@ public class SubsamplingScaleImageView extends View { public Paint tileBgPaint; // Volatile fields used to reduce object creation - ScaleAndTranslate satTemp; + public ScaleAndTranslate satTemp; public Matrix matrix; public RectF sRect; public float[] srcArray = new float[8]; @@ -357,39 +343,40 @@ public class SubsamplingScaleImageView extends View { // A global preference for bitmap format, available to decoder classes that respect it private static Bitmap.Config preferredBitmapConfig; + // Loader helper instance + public SubsamplingScaleImageViewLoader loader; + public SubsamplingScaleImageView(Context context, AttributeSet attr) { super(context, attr); + this.loader = new SubsamplingScaleImageViewLoader(this); // Initialize loader density = getResources().getDisplayMetrics().density; // Initialize minScale here now that 'this' is available this.minScale = SubsamplingScaleImageViewStateHelper.minScale(this); setMinimumDpi(160); setDoubleTapZoomDpi(160); setMinimumTileDpi(320); - setGestureDetector(context); + loader.setGestureDetector(context); this.handler = - new Handler( - new Handler.Callback() { - public boolean handleMessage(Message message) { - if (message.what == MESSAGE_LONG_CLICK - && onLongClickListener != null) { - maxTouchCount = 0; - SubsamplingScaleImageView.super.setOnLongClickListener( - onLongClickListener); - performLongClick(); - SubsamplingScaleImageView.super.setOnLongClickListener(null); - } - return true; - } - }); + new Handler( + new Handler.Callback() { + public boolean handleMessage(Message message) { + if (message.what == MESSAGE_LONG_CLICK && onLongClickListener != null) { + maxTouchCount = 0; + SubsamplingScaleImageView.super.setOnLongClickListener(onLongClickListener); + performLongClick(); + SubsamplingScaleImageView.super.setOnLongClickListener(null); + } + return true; + } + }); // Handle XML attributes if (attr != null) { - TypedArray typedAttr = - getContext().obtainStyledAttributes(attr, styleable.SubsamplingScaleImageView); + TypedArray typedAttr = getContext().obtainStyledAttributes(attr, styleable.SubsamplingScaleImageView); if (typedAttr.hasValue(styleable.SubsamplingScaleImageView_assetName)) { String assetName = typedAttr.getString(styleable.SubsamplingScaleImageView_assetName); if (assetName != null && assetName.length() > 0) { - setImage(ImageSource.asset(assetName).tilingEnabled()); + loader.setImage(ImageSource.asset(assetName).tilingEnabled()); } } @@ -397,7 +384,7 @@ public boolean handleMessage(Message message) { int resId = typedAttr.getResourceId(styleable.SubsamplingScaleImageView_src, 0); if (resId > 0) { - setImage(ImageSource.resource(resId).tilingEnabled()); + loader.setImage(ImageSource.resource(resId).tilingEnabled()); } } @@ -460,250 +447,13 @@ public final void setOrientation(int orientation) { if (!VALID_ORIENTATIONS.contains(orientation)) { throw new IllegalArgumentException("Invalid orientation: " + orientation); } + this.orientation = orientation; - reset(false); + loader.reset(false); invalidate(); requestLayout(); } - /** - * Set the image source from a bitmap, resource, asset, file or other URI. - * - * @param imageSource Image source. - */ - public final void setImage(@NonNull ImageSource imageSource) { - setImage(imageSource, null, null); - } - - /** - * Set the image source from a bitmap, resource, asset, file or other URI, starting with a given - * orientation setting, scale and center. This is the best method to use when you want scale and - * center to be restored after screen orientation change; it avoids any redundant loading of - * tiles in the wrong orientation. - * - * @param imageSource Image source. - * @param state State to be restored. Nullable. - */ - public final void setImage(@NonNull ImageSource imageSource, ImageViewState state) { - setImage(imageSource, null, state); - } - - /** - * Set the image source from a bitmap, resource, asset, file or other URI, providing a preview - * image to be displayed until the full size image is loaded. - * - *

You must declare the dimensions of the full size image by calling {@link - * ImageSource#dimensions(int, int)} on the imageSource object. The preview source will be - * ignored if you don't provide dimensions, and if you provide a bitmap for the full size image. - * - * @param imageSource Image source. Dimensions must be declared. - * @param previewSource Optional source for a preview image to be displayed and allow - * interaction while the full size image loads. - */ - public final void setImage(@NonNull ImageSource imageSource, ImageSource previewSource) { - setImage(imageSource, previewSource, null); - } - - /** - * Set the image source from a bitmap, resource, asset, file or other URI, providing a preview - * image to be displayed until the full size image is loaded, starting with a given orientation - * setting, scale and center. This is the best method to use when you want scale and center to - * be restored after screen orientation change; it avoids any redundant loading of tiles in the - * wrong orientation. - * - *

You must declare the dimensions of the full size image by calling {@link - * ImageSource#dimensions(int, int)} on the imageSource object. The preview source will be - * ignored if you don't provide dimensions, and if you provide a bitmap for the full size image. - * - * @param imageSource Image source. Dimensions must be declared. - * @param previewSource Optional source for a preview image to be displayed and allow - * interaction while the full size image loads. - * @param state State to be restored. Nullable. - */ - public final void setImage( - @NonNull ImageSource imageSource, ImageSource previewSource, ImageViewState state) { - // noinspection ConstantConditions - setAlpha(0); - if (imageSource == null) { - throw new NullPointerException("imageSource must not be null"); - } - - reset(true); - if (state != null) { - restoreState(state); - } - - if (previewSource != null) { - if (imageSource.getBitmap() != null) { - throw new IllegalArgumentException("Preview image cannot be used when a bitmap is provided for the main image"); - } - - if (imageSource.getSWidth() <= 0 || imageSource.getSHeight() <= 0) { - throw new IllegalArgumentException("Preview image cannot be used unless dimensions are provided for the main image"); - } - - this.sWidth = imageSource.getSWidth(); - this.sHeight = imageSource.getSHeight(); - this.pRegion = previewSource.getSRegion(); - - if (previewSource.getBitmap() != null) { - this.bitmapIsCached = previewSource.isCached(); - onPreviewLoaded(previewSource.getBitmap()); - } else { - Uri uri = previewSource.getUri(); - if (uri == null && previewSource.getResource() != null) { - uri = Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://" + getContext().getPackageName() + "/" + previewSource.getResource()); - } - BitmapLoadTask task = new BitmapLoadTask(this, getContext(), bitmapDecoderFactory, uri, true); - execute(task); - } - } - - if (imageSource.getBitmap() != null && imageSource.getSRegion() != null) { - onImageLoaded( - Bitmap.createBitmap( - imageSource.getBitmap(), - imageSource.getSRegion().left, - imageSource.getSRegion().top, - imageSource.getSRegion().width(), - imageSource.getSRegion().height()), - ORIENTATION_0, - false); - } else if (imageSource.getBitmap() != null) { - onImageLoaded(imageSource.getBitmap(), ORIENTATION_0, imageSource.isCached()); - } else { - sRegion = imageSource.getSRegion(); - uri = imageSource.getUri(); - - if (uri == null && imageSource.getResource() != null) { - uri = Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://" + getContext().getPackageName() + "/" + imageSource.getResource()); - } - - doLoader(imageSource, false); - } - } - - ImageSource savedImageSource; - - public void doLoader(boolean rapid) { - doLoader(savedImageSource, rapid); - } - - public void doLoader(ImageSource imageSource, boolean rapid) { - if (imageSource.getTile() || sRegion != null) { - - // Load the bitmap using tile decoding. - if (rapid) { - setRegionDecoderClass(RapidImageRegionDecoder.class); - TilesInitTask task = new TilesInitTask(this, getContext(), regionDecoderFactory, uri); - execute(task); - } else { - savedImageSource = imageSource; - TilesInitTask task = new TilesInitTask(this, getContext(), regionDecoderFactory, uri); - execute(task); - } - } else { - // Load the bitmap as a single image. - BitmapLoadTask task = new BitmapLoadTask(this, getContext(), bitmapDecoderFactory, uri, false); - execute(task); - } - } - - /** Reset all state before setting/changing image or setting new rotation. */ - private void reset(boolean newImage) { - debug("reset newImage=" + newImage); - scale = 0f; - scaleStart = 0f; - vTranslate = null; - vTranslateStart = null; - vTranslateBefore = null; - pendingScale = 0f; - sPendingCenter = null; - sRequestedCenter = null; - isZooming = false; - isPanning = false; - isQuickScaling = false; - maxTouchCount = 0; - fullImageSampleSize = 0; - vCenterStart = null; - vDistStart = 0; - quickScaleLastDistance = 0f; - quickScaleMoved = false; - quickScaleSCenter = null; - quickScaleVLastPoint = null; - quickScaleVStart = null; - anim = null; - satTemp = null; - matrix = null; - sRect = null; - if (newImage) { - uri = null; - decoderLock.writeLock().lock(); - try { - if (decoder != null) { - decoder.recycle(); - decoder = null; - } - } finally { - decoderLock.writeLock().unlock(); - } - - if (bitmap != null && !bitmapIsCached) { - bitmap.recycle(); - } - - if (bitmap != null && bitmapIsCached && onImageEventListener != null) { - onImageEventListener.onPreviewReleased(); - } - - sWidth = 0; - sHeight = 0; - sOrientation = 0; - sRegion = null; - pRegion = null; - readySent = false; - imageLoadedSent = false; - bitmap = null; - bitmapIsPreview = false; - bitmapIsCached = false; - } - - if (tileMap != null) { - for (Map.Entry> tileMapEntry : tileMap.entrySet()) { - for (Tile tile : tileMapEntry.getValue()) { - tile.visible = false; - if (tile.bitmap != null) { - tile.bitmap.recycle(); - tile.bitmap = null; - } - } - } - - tileMap = null; - } - - setGestureDetector(getContext()); - } - - // Public method to reset the gesture detector, used by the listener if needed - public void setGestureDetectorPublic(final Context context) { - setGestureDetector(context); - } - - private void setGestureDetector(final Context context) { - // Use the new external listener class directly. - // The methods onSingleTapConfirmed and onDoubleTap will be moved next. - this.detector = new GestureDetector(context, new ImageViewGestureListener(this, context)); - - singleDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() { - @Override - public boolean onSingleTapConfirmed(MotionEvent e) { - performClick(); - return true; - } - }); - } - /** * On resize, preserve center and scale. Various behaviours are possible, override this method * to use another. @@ -712,6 +462,7 @@ public boolean onSingleTapConfirmed(MotionEvent e) { protected void onSizeChanged(int w, int h, int oldw, int oldh) { debug("onSizeChanged %dx%d -> %dx%d", oldw, oldh, w, h); PointF sCenter = SubsamplingScaleImageViewStateHelper.getCenter(this); + if (readySent && sCenter != null) { this.anim = null; this.pendingScale = scale; @@ -918,6 +669,7 @@ public boolean checkImageLoaded() { preDraw(); imageLoadedSent = true; onImageLoaded(); + if (onImageEventListener != null) { onImageEventListener.onImageLoaded(); } @@ -947,47 +699,6 @@ public void createPaints() { } } - /** - * Called on first draw when the view has dimensions. Calculates the initial sample size and - * starts async loading of the base layer image - the whole source subsampled as necessary. - */ - public synchronized void initialiseBaseLayer(@NonNull Point maxTileDimensions) { - debug("initialiseBaseLayer maxTileDimensions=%dx%d", maxTileDimensions.x, maxTileDimensions.y); - - satTemp = new ScaleAndTranslate(0f, new PointF(0, 0)); - SubsamplingScaleImageViewDrawHelper.fitToBounds(this, true, satTemp); - - // Load double resolution - next level will be split into four tiles and at the center all - // four are required, so don't bother with tiling until the next level 16 tiles are needed. - fullImageSampleSize = calculateInSampleSize(satTemp.scale); - - if (fullImageSampleSize > 1) { - fullImageSampleSize /= 2; - } - - if (fullImageSampleSize == 1 && sRegion == null && SubsamplingScaleImageViewStateHelper.sWidth(this) < maxTileDimensions.x - && SubsamplingScaleImageViewStateHelper.sHeight(this) < maxTileDimensions.y) { - // Whole image is required at native resolution, and is smaller than the canvas max - // bitmap size. Use BitmapDecoder for better image support. - decoder.recycle(); - decoder = null; - BitmapLoadTask task = - new BitmapLoadTask(this, getContext(), bitmapDecoderFactory, uri, false); - execute(task); - } else { - initialiseTileMap(maxTileDimensions); - - List baseGrid = tileMap.get(fullImageSampleSize); - - for (Tile baseTile : baseGrid) { - TileLoadTask task = new TileLoadTask(this, decoder, baseTile); - execute(task); - } - - refreshRequiredTiles(true); - } - } - /** * Loads the optimum tiles for display at the current scale and translate, so the screen can be * filled with tiles that are at least as high resolution as the screen. Frees up bitmaps that @@ -1103,6 +814,7 @@ public void fitToBounds(boolean center) { SubsamplingScaleImageViewDrawHelper.fitToBounds(this, center, satTemp); scale = satTemp.scale; vTranslate.set(satTemp.vTranslate); + if (init && minimumScaleType != SCALE_TYPE_START) { vTranslate.set(SubsamplingScaleImageViewStateHelper.vTranslateForSCenter( this, SubsamplingScaleImageViewStateHelper.sWidth(this) / 2.0f, SubsamplingScaleImageViewStateHelper.sHeight(this) / 2.0f, scale)); @@ -1112,51 +824,12 @@ public void fitToBounds(boolean center) { /** * Once source image and view dimensions are known, creates a map of sample size to tile grid. */ - private void initialiseTileMap(Point maxTileDimensions) { + public void initialiseTileMap(Point maxTileDimensions) { TileManager.initialiseTileMap(this, maxTileDimensions); } /** Async task used to get image details without blocking the UI thread. */ - /** Called by worker task when decoder is ready and image size and EXIF orientation is known. */ - public synchronized void onTilesInited(ImageRegionDecoder decoder, int sWidth, int sHeight, int sOrientation) { - debug("onTilesInited sWidth=%d, sHeight=%d, sOrientation=%d", sWidth, sHeight, sOrientation); - - // If actual dimensions don't match the declared size, reset everything. - if (this.sWidth > 0 && this.sHeight > 0 && (this.sWidth != sWidth || this.sHeight != sHeight)) { - reset(false); - - if (bitmap != null) { - if (!bitmapIsCached) { - bitmap.recycle(); - } - - bitmap = null; - - if (onImageEventListener != null && bitmapIsCached) { - onImageEventListener.onPreviewReleased(); - } - - bitmapIsPreview = false; - bitmapIsCached = false; - } - } - - this.decoder = decoder; - this.sWidth = sWidth; - this.sHeight = sHeight; - this.sOrientation = sOrientation; - checkReady(); - - if (!checkImageLoaded() && maxTileWidth > 0 && maxTileWidth != TILE_SIZE_AUTO && maxTileHeight > 0 && maxTileHeight != TILE_SIZE_AUTO && getWidth() > 0 && getHeight() > 0) { - initialiseBaseLayer(new Point(maxTileWidth, maxTileHeight)); - } - - invalidate(); - requestLayout(); - animate().setInterpolator(new FastOutSlowInInterpolator()).alpha(1); - } - /** Async task used to load images without blocking the UI thread. */ public static class TileLoadTask extends AsyncTask { private final WeakReference viewRef; @@ -1183,7 +856,6 @@ protected Bitmap doInBackground(Void... params) { try { if (decoder.isReady()) { // Update tile's file sRect according to rotation - // Use helper method (view is SubsamplingScaleImageView instance) SubsamplingScaleImageViewStateHelper.fileSRect(view, tile.sRect, tile.fileSRect); if (view.sRegion != null) { tile.fileSRect.offset(view.sRegion.left, view.sRegion.top); @@ -1217,7 +889,7 @@ protected void onPostExecute(Bitmap bitmap) { if (bitmap != null) { tile.bitmap = bitmap; tile.loading = false; - subsamplingScaleImageView.onTileLoaded(); + subsamplingScaleImageView.loader.onTileLoaded(); } else if (exception != null && subsamplingScaleImageView.onImageEventListener != null) { subsamplingScaleImageView.onImageEventListener.onTileLoadError(exception); @@ -1226,30 +898,8 @@ protected void onPostExecute(Bitmap bitmap) { } } - /** Called by worker task when a tile has loaded. Redraws the view. */ - private synchronized void onTileLoaded() { - debug("onTileLoaded"); - checkReady(); - checkImageLoaded(); - - if (isBaseLayerReady() && bitmap != null) { - if (!bitmapIsCached) { - bitmap.recycle(); - } - bitmap = null; - if (onImageEventListener != null && bitmapIsCached) { - onImageEventListener.onPreviewReleased(); - } - bitmapIsPreview = false; - bitmapIsCached = false; - } - - invalidate(); - animate().setInterpolator(new FastOutSlowInInterpolator()).alpha(1); - } - /** Async task used to load bitmap without blocking the UI thread. */ - private static class BitmapLoadTask extends AsyncTask { + public static class BitmapLoadTask extends AsyncTask { private final WeakReference viewRef; private final WeakReference contextRef; private final WeakReference> decoderFactoryRef; @@ -1258,11 +908,10 @@ private static class BitmapLoadTask extends AsyncTask { private Bitmap bitmap; private Exception exception; - BitmapLoadTask(SubsamplingScaleImageView view, Context context, DecoderFactory decoderFactory, Uri source, boolean preview) { + public BitmapLoadTask(SubsamplingScaleImageView view, Context context, DecoderFactory decoderFactory, Uri source, boolean preview) { this.viewRef = new WeakReference<>(view); this.contextRef = new WeakReference<>(context); - this.decoderFactoryRef = - new WeakReference>(decoderFactory); + this.decoderFactoryRef = new WeakReference>(decoderFactory); this.source = source; this.preview = preview; } @@ -1274,6 +923,7 @@ protected Integer doInBackground(Void... params) { Context context = contextRef.get(); DecoderFactory decoderFactory = decoderFactoryRef.get(); SubsamplingScaleImageView view = viewRef.get(); + if (context != null && decoderFactory != null && view != null) { view.debug("BitmapLoadTask.doInBackground"); try { @@ -1282,7 +932,8 @@ protected Integer doInBackground(Void... params) { System.gc(); bitmap = decoderFactory.make().decode(context, source); } - return view.getExifOrientation(context, sourceUri); + + return view.loader.getExifOrientation(context, sourceUri); } } catch (Exception e) { Log.e(TAG, "Failed to load bitmap", e); @@ -1297,12 +948,13 @@ protected Integer doInBackground(Void... params) { @Override protected void onPostExecute(Integer orientation) { SubsamplingScaleImageView subsamplingScaleImageView = viewRef.get(); + if (subsamplingScaleImageView != null) { if (bitmap != null && orientation != null) { if (preview) { - subsamplingScaleImageView.onPreviewLoaded(bitmap); + subsamplingScaleImageView.loader.onPreviewLoaded(bitmap); } else { - subsamplingScaleImageView.onImageLoaded(bitmap, orientation, false); + subsamplingScaleImageView.loader.onImageLoaded(bitmap, orientation, false); } } else if (exception != null && subsamplingScaleImageView.onImageEventListener != null) { @@ -1317,120 +969,6 @@ protected void onPostExecute(Integer orientation) { } } - /** Called by worker task when preview image is loaded. */ - private synchronized void onPreviewLoaded(Bitmap previewBitmap) { - debug("onPreviewLoaded"); - if (bitmap != null || imageLoadedSent) { - previewBitmap.recycle(); - return; - } - if (pRegion != null) { - bitmap = - Bitmap.createBitmap( - previewBitmap, - pRegion.left, - pRegion.top, - pRegion.width(), - pRegion.height()); - } else { - bitmap = previewBitmap; - } - bitmapIsPreview = true; - if (checkReady()) { - invalidate(); - requestLayout(); - } - } - - /** Called by worker task when full size image bitmap is ready (tiling is disabled). */ - private synchronized void onImageLoaded( - Bitmap bitmap, int sOrientation, boolean bitmapIsCached) { - debug("onImageLoaded"); - - // If actual dimensions don't match the declared size, reset everything. - if (this.sWidth > 0 && this.sHeight > 0 && (this.sWidth != bitmap.getWidth() || this.sHeight != bitmap.getHeight())) { - reset(false); - } - - if (this.bitmap != null && !this.bitmapIsCached) { - this.bitmap.recycle(); - } - - if (this.bitmap != null && this.bitmapIsCached && onImageEventListener != null) { - onImageEventListener.onPreviewReleased(); - } - - this.bitmapIsPreview = false; - this.bitmapIsCached = bitmapIsCached; - this.bitmap = bitmap; - this.sWidth = bitmap.getWidth(); - this.sHeight = bitmap.getHeight(); - this.sOrientation = sOrientation; - boolean ready = checkReady(); - boolean imageLoaded = checkImageLoaded(); - - if (ready || imageLoaded) { - invalidate(); - requestLayout(); - } - - animate().setInterpolator(new FastOutSlowInInterpolator()).alpha(1); - } - - /** - * Helper method for load tasks. Examines the EXIF info on the image file to determine the - * orientation. This will only work for external files, not assets, resources or other URIs. - */ - @AnyThread - public int getExifOrientation(Context context, String sourceUri) { - int exifOrientation = ORIENTATION_0; - if (sourceUri.startsWith(ContentResolver.SCHEME_CONTENT)) { - Cursor cursor = null; - try { - String[] columns = {MediaStore.Images.Media.ORIENTATION}; - cursor = context.getContentResolver().query(Uri.parse(sourceUri), columns, null, null, null); - - if (cursor != null) { - if (cursor.moveToFirst()) { - int orientation = cursor.getInt(0); - - if (VALID_ORIENTATIONS.contains(orientation) && orientation != ORIENTATION_USE_EXIF) { - exifOrientation = orientation; - } else { - Log.w(TAG, "Unsupported orientation: " + orientation); - } - } - } - } catch (Exception e) { - Log.w(TAG, "Could not get orientation of image from media store"); - } finally { - if (cursor != null) { - cursor.close(); - } - } - } else if (sourceUri.startsWith(ImageSource.FILE_SCHEME) - && !sourceUri.startsWith(ImageSource.ASSET_SCHEME)) { - try { - ExifInterface exifInterface = new ExifInterface(sourceUri.substring(ImageSource.FILE_SCHEME.length() - 1)); - int orientationAttr = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL); - if (orientationAttr == ExifInterface.ORIENTATION_NORMAL || orientationAttr == ExifInterface.ORIENTATION_UNDEFINED) { - exifOrientation = ORIENTATION_0; - } else if (orientationAttr == ExifInterface.ORIENTATION_ROTATE_90) { - exifOrientation = ORIENTATION_90; - } else if (orientationAttr == ExifInterface.ORIENTATION_ROTATE_180) { - exifOrientation = ORIENTATION_180; - } else if (orientationAttr == ExifInterface.ORIENTATION_ROTATE_270) { - exifOrientation = ORIENTATION_270; - } else { - Log.w(TAG, "Unsupported EXIF orientation: " + orientationAttr); - } - } catch (Exception e) { - Log.w(TAG, "Could not get EXIF orientation of image"); - } - } - return exifOrientation; - } - public void execute(AsyncTask asyncTask) { asyncTask.executeOnExecutor(executor); } @@ -1474,7 +1012,7 @@ public ScaleAndTranslate(float scale, PointF vTranslate) { } /** Set scale, center and orientation from saved state. */ - private void restoreState(ImageViewState state) { + public void restoreState(ImageViewState state) { if (state != null && VALID_ORIENTATIONS.contains(state.getOrientation())) { this.orientation = state.getOrientation(); this.pendingScale = state.getScale(); @@ -1521,6 +1059,7 @@ public Point getMaxBitmapDimensions(Canvas canvas) { private float distance(float x0, float x1, float y0, float y1) { float x = x0 - x1; float y = y0 - y1; + return (float) Math.sqrt(x * x + y * y); } @@ -1531,7 +1070,7 @@ private float distance(float x0, float x1, float y0, float y1) { * these yourself if required. */ public void recycle() { - reset(true); + loader.reset(true); bitmapPaint = null; debugTextPaint = null; debugLinePaint = null; @@ -1591,6 +1130,7 @@ public float easeInOutQuad(long time, float from, float change, long duration) { return (change / 2f * timeF * timeF) + from; } else { timeF--; + return (-change / 2f) * (timeF * (timeF - 2) - 1) + from; } } @@ -1823,7 +1363,7 @@ public void setMinimumTileDpi(int minimumTileDpi) { this.minimumTileDpi = (int) Math.min(averageDpi, minimumTileDpi); if (isReady()) { - reset(false); + loader.reset(false); invalidate(); } } @@ -1978,6 +1518,7 @@ public final ImageViewState getState() { // noinspection ConstantConditions return new ImageViewState(getScale(), SubsamplingScaleImageViewStateHelper.getCenter(this), getOrientation()); } + return null; } @@ -2423,4 +1964,4 @@ public void onCenterChanged(PointF newCenter, int origin) {} @Override public void onScaleChanged(float newScale, int origin) {} } -} \ No newline at end of file +} diff --git a/app/src/main/java/me/edgan/redditslide/util/ImageViewGestureListener.java b/app/src/main/java/me/edgan/redditslide/util/ImageViewGestureListener.java index 8543c447a..1f547b0b4 100644 --- a/app/src/main/java/me/edgan/redditslide/util/ImageViewGestureListener.java +++ b/app/src/main/java/me/edgan/redditslide/util/ImageViewGestureListener.java @@ -54,7 +54,7 @@ public boolean onDoubleTap(MotionEvent e) { if (view.zoomEnabled && view.readySent && view.vTranslate != null) { // Hacky solution for #15 - after a double tap the GestureDetector gets in a state where the next fling is ignored, so here we replace it with a new one. // Use the public method we added to the view. - view.setGestureDetectorPublic(context); + view.loader.setGestureDetector(context); if (view.quickScaleEnabled) { // Store quick scale params. This will become either a double tap zoom or a quick scale depending on whether the user swipes. @@ -64,7 +64,7 @@ public boolean onDoubleTap(MotionEvent e) { view.isQuickScaling = true; view.isZooming = true; view.quickScaleLastDistance = -1F; - view.quickScaleSCenter = SubsamplingScaleImageViewStateHelper.viewToSourceCoord(view, view.vCenterStart); // Use helper + view.quickScaleSCenter = SubsamplingScaleImageViewStateHelper.viewToSourceCoord(view, view.vCenterStart); view.quickScaleVStart = new PointF(e.getX(), e.getY()); view.quickScaleVLastPoint = new PointF(view.quickScaleSCenter.x, view.quickScaleSCenter.y); view.quickScaleMoved = false; @@ -73,7 +73,7 @@ public boolean onDoubleTap(MotionEvent e) { return false; } else { // Start double tap zoom animation. - view.doubleTapZoom(SubsamplingScaleImageViewStateHelper.viewToSourceCoord(view, new PointF(e.getX(), e.getY())), new PointF(e.getX(), e.getY())); // Use helper + view.doubleTapZoom(SubsamplingScaleImageViewStateHelper.viewToSourceCoord(view, new PointF(e.getX(), e.getY())), new PointF(e.getX(), e.getY())); return true; } diff --git a/app/src/main/java/me/edgan/redditslide/util/SubsamplingScaleImageViewDrawHelper.java b/app/src/main/java/me/edgan/redditslide/util/SubsamplingScaleImageViewDrawHelper.java index eddbfff1b..91f06ff2e 100644 --- a/app/src/main/java/me/edgan/redditslide/util/SubsamplingScaleImageViewDrawHelper.java +++ b/app/src/main/java/me/edgan/redditslide/util/SubsamplingScaleImageViewDrawHelper.java @@ -30,7 +30,7 @@ public static void drawContent(SubsamplingScaleImageView view, Canvas canvas) { // When using tiles, on first render with no tile map ready, initialise it and kick off async base image loading. if (view.tileMap == null && view.decoder != null) { - view.initialiseBaseLayer(view.getMaxBitmapDimensions(canvas)); + view.loader.initialiseBaseLayer(view.getMaxBitmapDimensions(canvas)); } // If image has been loaded or supplied as a bitmap, onDraw may be the first time the view @@ -62,8 +62,8 @@ public static void drawContent(SubsamplingScaleImageView view, Canvas canvas) { float vFocusNowY = view.ease(view.anim.easing, scaleElapsed, view.anim.vFocusStart.y, view.anim.vFocusEnd.y - view.anim.vFocusStart.y, view.anim.duration); // Find out where the focal point is at this scale and adjust its position to follow the animation path - view.vTranslate.x -= SubsamplingScaleImageViewStateHelper.sourceToViewX(view, view.anim.sCenterEnd.x) - vFocusNowX; // Use helper - view.vTranslate.y -= SubsamplingScaleImageViewStateHelper.sourceToViewY(view, view.anim.sCenterEnd.y) - vFocusNowY; // Use helper + view.vTranslate.x -= SubsamplingScaleImageViewStateHelper.sourceToViewX(view, view.anim.sCenterEnd.x) - vFocusNowX; + view.vTranslate.y -= SubsamplingScaleImageViewStateHelper.sourceToViewY(view, view.anim.sCenterEnd.y) - vFocusNowY; // For translate anims, showing the image non-centered is never allowed, for scaling anims it is during the animation. view.fitToBounds(finished || (view.anim.scaleStart == view.anim.scaleEnd)); @@ -108,7 +108,7 @@ public static void drawContent(SubsamplingScaleImageView view, Canvas canvas) { for (Map.Entry> tileMapEntry : view.tileMap.entrySet()) { if (tileMapEntry.getKey() == sampleSize || hasMissingTiles) { for (SubsamplingScaleImageView.Tile tile : tileMapEntry.getValue()) { - SubsamplingScaleImageViewStateHelper.sourceToViewRect(view, tile.sRect, tile.vRect); // Use helper + SubsamplingScaleImageViewStateHelper.sourceToViewRect(view, tile.sRect, tile.vRect); if (!tile.loading && tile.bitmap != null) { if (view.tileBgPaint != null) { canvas.drawRect(tile.vRect, view.tileBgPaint); @@ -130,7 +130,7 @@ public static void drawContent(SubsamplingScaleImageView view, Canvas canvas) { 0, tile.bitmap.getHeight()); - if (SubsamplingScaleImageViewStateHelper.getRequiredRotation(view) == SubsamplingScaleImageView.ORIENTATION_0) { // Use helper + if (SubsamplingScaleImageViewStateHelper.getRequiredRotation(view) == SubsamplingScaleImageView.ORIENTATION_0) { setMatrixArray( view.dstArray, tile.vRect.left, @@ -141,7 +141,7 @@ public static void drawContent(SubsamplingScaleImageView view, Canvas canvas) { tile.vRect.bottom, tile.vRect.left, tile.vRect.bottom); - } else if (SubsamplingScaleImageViewStateHelper.getRequiredRotation(view) == SubsamplingScaleImageView.ORIENTATION_90) { // Use helper + } else if (SubsamplingScaleImageViewStateHelper.getRequiredRotation(view) == SubsamplingScaleImageView.ORIENTATION_90) { setMatrixArray( view.dstArray, tile.vRect.right, @@ -152,7 +152,7 @@ public static void drawContent(SubsamplingScaleImageView view, Canvas canvas) { tile.vRect.bottom, tile.vRect.left, tile.vRect.top); - } else if (SubsamplingScaleImageViewStateHelper.getRequiredRotation(view) == SubsamplingScaleImageView.ORIENTATION_180) { // Use helper + } else if (SubsamplingScaleImageViewStateHelper.getRequiredRotation(view) == SubsamplingScaleImageView.ORIENTATION_180) { setMatrixArray( view.dstArray, tile.vRect.right, @@ -163,7 +163,7 @@ public static void drawContent(SubsamplingScaleImageView view, Canvas canvas) { tile.vRect.top, tile.vRect.right, tile.vRect.top); - } else if (SubsamplingScaleImageViewStateHelper.getRequiredRotation(view) == SubsamplingScaleImageView.ORIENTATION_270) { // Use helper + } else if (SubsamplingScaleImageViewStateHelper.getRequiredRotation(view) == SubsamplingScaleImageView.ORIENTATION_270) { setMatrixArray( view.dstArray, tile.vRect.left, @@ -224,14 +224,14 @@ public static void drawContent(SubsamplingScaleImageView view, Canvas canvas) { view.matrix.reset(); view.matrix.postScale(xScale, yScale); - view.matrix.postRotate(SubsamplingScaleImageViewStateHelper.getRequiredRotation(view)); // Use helper + view.matrix.postRotate(SubsamplingScaleImageViewStateHelper.getRequiredRotation(view)); view.matrix.postTranslate(view.vTranslate.x, view.vTranslate.y); - if (SubsamplingScaleImageViewStateHelper.getRequiredRotation(view) == SubsamplingScaleImageView.ORIENTATION_180) { // Use helper + if (SubsamplingScaleImageViewStateHelper.getRequiredRotation(view) == SubsamplingScaleImageView.ORIENTATION_180) { view.matrix.postTranslate(view.scale * view.sWidth, view.scale * view.sHeight); - } else if (SubsamplingScaleImageViewStateHelper.getRequiredRotation(view) == SubsamplingScaleImageView.ORIENTATION_90) { // Use helper + } else if (SubsamplingScaleImageViewStateHelper.getRequiredRotation(view) == SubsamplingScaleImageView.ORIENTATION_90) { view.matrix.postTranslate(view.scale * view.sHeight, 0); - } else if (SubsamplingScaleImageViewStateHelper.getRequiredRotation(view) == SubsamplingScaleImageView.ORIENTATION_270) { // Use helper + } else if (SubsamplingScaleImageViewStateHelper.getRequiredRotation(view) == SubsamplingScaleImageView.ORIENTATION_270) { view.matrix.postTranslate(0, view.scale * view.sWidth); } @@ -255,7 +255,7 @@ public static void drawContent(SubsamplingScaleImageView view, Canvas canvas) { "Scale: " + String.format(Locale.ENGLISH, "%.2f", view.scale) + " (" - + String.format(Locale.ENGLISH, "%.2f", SubsamplingScaleImageViewStateHelper.minScale(view)) // Use helper + + String.format(Locale.ENGLISH, "%.2f", SubsamplingScaleImageViewStateHelper.minScale(view)) + " - " + String.format(Locale.ENGLISH, "%.2f", view.maxScale) + ")", @@ -281,9 +281,9 @@ public static void drawContent(SubsamplingScaleImageView view, Canvas canvas) { px(view, 45), view.debugTextPaint); if (view.anim != null) { - PointF vCenterStart = SubsamplingScaleImageViewStateHelper.sourceToViewCoord(view, view.anim.sCenterStart); // Use helper - PointF vCenterEndRequested = SubsamplingScaleImageViewStateHelper.sourceToViewCoord(view, view.anim.sCenterEndRequested); // Use helper - PointF vCenterEnd = SubsamplingScaleImageViewStateHelper.sourceToViewCoord(view, view.anim.sCenterEnd); // Use helper + PointF vCenterStart = SubsamplingScaleImageViewStateHelper.sourceToViewCoord(view, view.anim.sCenterStart); + PointF vCenterEndRequested = SubsamplingScaleImageViewStateHelper.sourceToViewCoord(view, view.anim.sCenterEndRequested); + PointF vCenterEnd = SubsamplingScaleImageViewStateHelper.sourceToViewCoord(view, view.anim.sCenterEnd); // noinspection ConstantConditions canvas.drawCircle(vCenterStart.x, vCenterStart.y, px(view, 10), view.debugLinePaint); view.debugLinePaint.setColor(Color.RED); @@ -302,7 +302,7 @@ public static void drawContent(SubsamplingScaleImageView view, Canvas canvas) { } if (view.quickScaleSCenter != null) { view.debugLinePaint.setColor(Color.BLUE); - canvas.drawCircle(SubsamplingScaleImageViewStateHelper.sourceToViewX(view, view.quickScaleSCenter.x), SubsamplingScaleImageViewStateHelper.sourceToViewY(view, view.quickScaleSCenter.y), px(view, 35), view.debugLinePaint); // Use helper + canvas.drawCircle(SubsamplingScaleImageViewStateHelper.sourceToViewX(view, view.quickScaleSCenter.x), SubsamplingScaleImageViewStateHelper.sourceToViewY(view, view.quickScaleSCenter.y), px(view, 35), view.debugLinePaint); } if (view.quickScaleVStart != null && view.isQuickScaling) { view.debugLinePaint.setColor(Color.CYAN); @@ -312,7 +312,7 @@ public static void drawContent(SubsamplingScaleImageView view, Canvas canvas) { } } /** For debug overlays. Scale pixel value according to screen density. */ -public static int px(SubsamplingScaleImageView view, int px) { // Made public +public static int px(SubsamplingScaleImageView view, int px) { return (int) (view.density * px); } @@ -356,10 +356,10 @@ public static void fitToBounds(SubsamplingScaleImageView view, boolean center, S } PointF vTranslate = sat.vTranslate; - // Use view's limitedScale method - ensure it's accessible (made public in next step) - float scale = SubsamplingScaleImageViewStateHelper.limitedScale(view, sat.scale); // Use helper - float scaleWidth = scale * SubsamplingScaleImageViewStateHelper.sWidth(view); // Use helper - float scaleHeight = scale * SubsamplingScaleImageViewStateHelper.sHeight(view); // Use helper + // Use view's limitedScale method - ensure it's accessible + float scale = SubsamplingScaleImageViewStateHelper.limitedScale(view, sat.scale); + float scaleWidth = scale * SubsamplingScaleImageViewStateHelper.sWidth(view); + float scaleHeight = scale * SubsamplingScaleImageViewStateHelper.sHeight(view); if (view.panLimit == SubsamplingScaleImageView.PAN_LIMIT_CENTER && view.isReady()) { vTranslate.x = Math.max(vTranslate.x, view.getWidth() / 2.0f - scaleWidth); diff --git a/app/src/main/java/me/edgan/redditslide/util/SubsamplingScaleImageViewLoader.java b/app/src/main/java/me/edgan/redditslide/util/SubsamplingScaleImageViewLoader.java new file mode 100644 index 000000000..cac70a490 --- /dev/null +++ b/app/src/main/java/me/edgan/redditslide/util/SubsamplingScaleImageViewLoader.java @@ -0,0 +1,518 @@ +package me.edgan.redditslide.util; + +import android.content.ContentResolver; +import android.content.Context; +import android.database.Cursor; +import android.graphics.Bitmap; +import android.graphics.Point; +import android.graphics.PointF; +import android.net.Uri; +import android.provider.MediaStore; +import android.util.Log; +import android.view.GestureDetector; +import android.view.MotionEvent; + +import com.davemorrissey.labs.subscaleview.ImageViewState; +import com.davemorrissey.labs.subscaleview.decoder.ImageRegionDecoder; + +import java.util.List; + +import androidx.interpolator.view.animation.FastOutSlowInInterpolator; + +import androidx.annotation.AnyThread; +import androidx.annotation.NonNull; +import androidx.exifinterface.media.ExifInterface; + +import com.davemorrissey.labs.subscaleview.decoder.ImageRegionDecoder; + +import java.util.Arrays; +import java.util.List; + +import me.edgan.redditslide.Views.ImageSource; +import me.edgan.redditslide.Views.RapidImageRegionDecoder; +import me.edgan.redditslide.Views.SubsamplingScaleImageView; + +/** + * Helper class for SubsamplingScaleImageView responsible for + * image loading, initialization, and related callbacks. + */ +public class SubsamplingScaleImageViewLoader { + + private final SubsamplingScaleImageView view; + public ImageSource savedImageSource; + + public SubsamplingScaleImageViewLoader(@NonNull SubsamplingScaleImageView view) { + this.view = view; + } + + // Methods will be moved here... + + /** + * Helper method for load tasks. Examines the EXIF info on the image file to determine the + * orientation. This will only work for external files, not assets, resources or other URIs. + */ + @AnyThread + public int getExifOrientation(Context context, String sourceUri) { + // Need access to constants from the view + final List VALID_ORIENTATIONS = Arrays.asList( + SubsamplingScaleImageView.ORIENTATION_0, + SubsamplingScaleImageView.ORIENTATION_90, + SubsamplingScaleImageView.ORIENTATION_180, + SubsamplingScaleImageView.ORIENTATION_270, + SubsamplingScaleImageView.ORIENTATION_USE_EXIF + ); + + int exifOrientation = SubsamplingScaleImageView.ORIENTATION_0; + if (sourceUri.startsWith(ContentResolver.SCHEME_CONTENT)) { + Cursor cursor = null; + try { + String[] columns = {MediaStore.Images.Media.ORIENTATION}; + cursor = context.getContentResolver().query(Uri.parse(sourceUri), columns, null, null, null); + + if (cursor != null) { + if (cursor.moveToFirst()) { + int orientation = cursor.getInt(0); + + if (VALID_ORIENTATIONS.contains(orientation) && orientation != SubsamplingScaleImageView.ORIENTATION_USE_EXIF) { + exifOrientation = orientation; + } else { + Log.w(SubsamplingScaleImageView.TAG, "Unsupported orientation: " + orientation); + } + } + } + } catch (Exception e) { + Log.w(SubsamplingScaleImageView.TAG, "Could not get orientation of image from media store"); + } finally { + if (cursor != null) { + cursor.close(); + } + } + } else if (sourceUri.startsWith(ImageSource.FILE_SCHEME) && !sourceUri.startsWith(ImageSource.ASSET_SCHEME)) { + try { + ExifInterface exifInterface = new ExifInterface(sourceUri.substring(ImageSource.FILE_SCHEME.length() - 1)); + int orientationAttr = exifInterface.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL); + if (orientationAttr == ExifInterface.ORIENTATION_NORMAL || orientationAttr == ExifInterface.ORIENTATION_UNDEFINED) { + exifOrientation = SubsamplingScaleImageView.ORIENTATION_0; + } else if (orientationAttr == ExifInterface.ORIENTATION_ROTATE_90) { + exifOrientation = SubsamplingScaleImageView.ORIENTATION_90; + } else if (orientationAttr == ExifInterface.ORIENTATION_ROTATE_180) { + exifOrientation = SubsamplingScaleImageView.ORIENTATION_180; + } else if (orientationAttr == ExifInterface.ORIENTATION_ROTATE_270) { + exifOrientation = SubsamplingScaleImageView.ORIENTATION_270; + } else { + Log.w(SubsamplingScaleImageView.TAG, "Unsupported EXIF orientation: " + orientationAttr); + } + } catch (Exception e) { + Log.w(SubsamplingScaleImageView.TAG, "Could not get EXIF orientation of image"); + } + } + return exifOrientation; + } + + + /** Called by worker task when decoder is ready and image size and EXIF orientation is known. */ + public synchronized void onTilesInited(ImageRegionDecoder decoder, int sWidth, int sHeight, int sOrientation) { + view.debug("onTilesInited sWidth=%d, sHeight=%d, sOrientation=%d", sWidth, sHeight, sOrientation); + + // If actual dimensions don't match the declared size, reset everything. + if (view.sWidth > 0 && view.sHeight > 0 && (view.sWidth != sWidth || view.sHeight != sHeight)) { + this.reset(false); // Call loader's own reset method + + if (view.bitmap != null) { + if (!view.bitmapIsCached) { + view.bitmap.recycle(); + } + + view.bitmap = null; + + if (view.onImageEventListener != null && view.bitmapIsCached) { + view.onImageEventListener.onPreviewReleased(); + } + + view.bitmapIsPreview = false; + view.bitmapIsCached = false; + } + } + + view.decoder = decoder; + view.sWidth = sWidth; + view.sHeight = sHeight; + view.sOrientation = sOrientation; + view.checkReady(); + + if (!view.checkImageLoaded() && view.maxTileWidth > 0 && view.maxTileWidth != SubsamplingScaleImageView.TILE_SIZE_AUTO && view.maxTileHeight > 0 && view.maxTileHeight != SubsamplingScaleImageView.TILE_SIZE_AUTO && view.getWidth() > 0 && view.getHeight() > 0) { + // Need to make initialiseBaseLayer accessible or move it too. Let's assume it will be moved later. + // For now, we might need to make it public/package-private or call via loader if moved. + // Temporarily making it public for this step. + this.initialiseBaseLayer(new Point(view.maxTileWidth, view.maxTileHeight)); // Call loader's own method + } + + view.invalidate(); + view.requestLayout(); + view.animate().setInterpolator(new FastOutSlowInInterpolator()).alpha(1); + } + + /** Called by worker task when a tile has loaded. Redraws the view. */ + public synchronized void onTileLoaded() { + view.debug("onTileLoaded"); + view.checkReady(); + view.checkImageLoaded(); + + if (view.isBaseLayerReady() && view.bitmap != null) { + if (!view.bitmapIsCached) { + view.bitmap.recycle(); + } + + view.bitmap = null; + + if (view.onImageEventListener != null && view.bitmapIsCached) { + view.onImageEventListener.onPreviewReleased(); + } + + view.bitmapIsPreview = false; + view.bitmapIsCached = false; + } + + view.invalidate(); + // The original method didn't have the alpha animation here, only in onTilesInited/onImageLoaded. + // Keeping it consistent with the original onTileLoaded. + } + + /** Called by worker task when preview image is loaded. */ + public synchronized void onPreviewLoaded(Bitmap previewBitmap) { + view.debug("onPreviewLoaded"); + + if (view.bitmap != null || view.imageLoadedSent) { + previewBitmap.recycle(); + return; + } + + if (view.pRegion != null) { + view.bitmap = + Bitmap.createBitmap( + previewBitmap, + view.pRegion.left, + view.pRegion.top, + view.pRegion.width(), + view.pRegion.height()); + } else { + view.bitmap = previewBitmap; + } + + view.bitmapIsPreview = true; + + if (view.checkReady()) { + view.invalidate(); + view.requestLayout(); + } + } + + /** Called by worker task when full size image bitmap is ready (tiling is disabled). */ + public synchronized void onImageLoaded(Bitmap bitmap, int sOrientation, boolean bitmapIsCached) { + view.debug("onImageLoaded"); + + // If actual dimensions don't match the declared size, reset everything. + if (view.sWidth > 0 && view.sHeight > 0 && (view.sWidth != bitmap.getWidth() || view.sHeight != bitmap.getHeight())) { + this.reset(false); // Call loader's own reset method + } + + if (view.bitmap != null && !view.bitmapIsCached) { + view.bitmap.recycle(); + } + + if (view.bitmap != null && view.bitmapIsCached && view.onImageEventListener != null) { + view.onImageEventListener.onPreviewReleased(); + } + + view.bitmapIsPreview = false; + view.bitmapIsCached = bitmapIsCached; + view.bitmap = bitmap; + view.sWidth = bitmap.getWidth(); + view.sHeight = bitmap.getHeight(); + view.sOrientation = sOrientation; + boolean ready = view.checkReady(); + boolean imageLoaded = view.checkImageLoaded(); + + if (ready || imageLoaded) { + view.invalidate(); + view.requestLayout(); + } + + view.animate().setInterpolator(new FastOutSlowInInterpolator()).alpha(1); + } + + /** Reset all state before setting/changing image or setting new rotation. */ + public void reset(boolean newImage) { + view.debug("reset newImage=" + newImage); + view.scale = 0f; + view.scaleStart = 0f; + view.vTranslate = null; + view.vTranslateStart = null; + view.vTranslateBefore = null; + view.pendingScale = 0f; + view.sPendingCenter = null; + view.sRequestedCenter = null; + view.isZooming = false; + view.isPanning = false; + view.isQuickScaling = false; + view.maxTouchCount = 0; + view.fullImageSampleSize = 0; + view.vCenterStart = null; + view.vDistStart = 0; + view.quickScaleLastDistance = 0f; + view.quickScaleMoved = false; + view.quickScaleSCenter = null; + view.quickScaleVLastPoint = null; + view.quickScaleVStart = null; + view.anim = null; + view.satTemp = null; + view.matrix = null; + view.sRect = null; + + if (newImage) { + view.uri = null; + view.decoderLock.writeLock().lock(); + try { + if (view.decoder != null) { + view.decoder.recycle(); + view.decoder = null; + } + } finally { + view.decoderLock.writeLock().unlock(); + } + + if (view.bitmap != null && !view.bitmapIsCached) { + view.bitmap.recycle(); + } + + if (view.bitmap != null && view.bitmapIsCached && view.onImageEventListener != null) { + view.onImageEventListener.onPreviewReleased(); + } + + view.sWidth = 0; + view.sHeight = 0; + view.sOrientation = 0; + view.sRegion = null; + view.pRegion = null; + view.readySent = false; + view.imageLoadedSent = false; + view.bitmap = null; + view.bitmapIsPreview = false; + view.bitmapIsCached = false; + } + + if (view.tileMap != null) { + for (java.util.Map.Entry> tileMapEntry : view.tileMap.entrySet()) { + for (SubsamplingScaleImageView.Tile tile : tileMapEntry.getValue()) { + tile.visible = false; + if (tile.bitmap != null) { + tile.bitmap.recycle(); + tile.bitmap = null; + } + } + } + view.tileMap = null; + } + + this.setGestureDetector(view.getContext()); + } + + // --- setImage and doLoader methods --- + + /** + * Set the image source from a bitmap, resource, asset, file or other URI. + * + * @param imageSource Image source. + */ + public final void setImage(@NonNull ImageSource imageSource) { + setImage(imageSource, null, null); + } + + /** + * Set the image source from a bitmap, resource, asset, file or other URI, starting with a given + * orientation setting, scale and center. This is the best method to use when you want scale and + * center to be restored after screen orientation change; it avoids any redundant loading of + * tiles in the wrong orientation. + * + * @param imageSource Image source. + * @param state State to be restored. Nullable. + */ + public final void setImage(@NonNull ImageSource imageSource, ImageViewState state) { + setImage(imageSource, null, state); + } + + /** + * Set the image source from a bitmap, resource, asset, file or other URI, providing a preview + * image to be displayed until the full size image is loaded. + * + *

You must declare the dimensions of the full size image by calling {@link + * ImageSource#dimensions(int, int)} on the imageSource object. The preview source will be + * ignored if you don't provide dimensions, and if you provide a bitmap for the full size image. + * + * @param imageSource Image source. Dimensions must be declared. + * @param previewSource Optional source for a preview image to be displayed and allow + * interaction while the full size image loads. + */ + public final void setImage(@NonNull ImageSource imageSource, ImageSource previewSource) { + setImage(imageSource, previewSource, null); + } + + /** + * Set the image source from a bitmap, resource, asset, file or other URI, providing a preview + * image to be displayed until the full size image is loaded, starting with a given orientation + * setting, scale and center. This is the best method to use when you want scale and center to + * be restored after screen orientation change; it avoids any redundant loading of tiles in the + * wrong orientation. + * + *

You must declare the dimensions of the full size image by calling {@link + * ImageSource#dimensions(int, int)} on the imageSource object. The preview source will be + * ignored if you don't provide dimensions, and if you provide a bitmap for the full size image. + * + * @param imageSource Image source. Dimensions must be declared. + * @param previewSource Optional source for a preview image to be displayed and allow + * interaction while the full size image loads. + * @param state State to be restored. Nullable. + */ + public final void setImage( + @NonNull ImageSource imageSource, ImageSource previewSource, ImageViewState state) { + // noinspection ConstantConditions + view.setAlpha(0); + + if (imageSource == null) { + throw new NullPointerException("imageSource must not be null"); + } + + reset(true); + + if (state != null) { + view.restoreState(state); + } + + if (previewSource != null) { + if (imageSource.getBitmap() != null) { + throw new IllegalArgumentException("Preview image cannot be used when a bitmap is provided for the main image"); + } + + if (imageSource.getSWidth() <= 0 || imageSource.getSHeight() <= 0) { + throw new IllegalArgumentException("Preview image cannot be used unless dimensions are provided for the main image"); + } + + view.sWidth = imageSource.getSWidth(); + view.sHeight = imageSource.getSHeight(); + view.pRegion = previewSource.getSRegion(); // pRegion is public + + if (previewSource.getBitmap() != null) { + view.bitmapIsCached = previewSource.isCached(); + onPreviewLoaded(previewSource.getBitmap()); + } else { + Uri uri = previewSource.getUri(); + if (uri == null && previewSource.getResource() != null) { + uri = Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://" + view.getContext().getPackageName() + "/" + previewSource.getResource()); + } + SubsamplingScaleImageView.BitmapLoadTask task = new SubsamplingScaleImageView.BitmapLoadTask(view, view.getContext(), view.bitmapDecoderFactory, uri, true); // bitmapDecoderFactory is public + view.execute(task); + } + } + + if (imageSource.getBitmap() != null && imageSource.getSRegion() != null) { + onImageLoaded( + Bitmap.createBitmap( + imageSource.getBitmap(), + imageSource.getSRegion().left, + imageSource.getSRegion().top, + imageSource.getSRegion().width(), + imageSource.getSRegion().height()), + SubsamplingScaleImageView.ORIENTATION_0, + false); + } else if (imageSource.getBitmap() != null) { + onImageLoaded(imageSource.getBitmap(), SubsamplingScaleImageView.ORIENTATION_0, imageSource.isCached()); + } else { + view.sRegion = imageSource.getSRegion(); + view.uri = imageSource.getUri(); // uri is public + + if (view.uri == null && imageSource.getResource() != null) { + view.uri = Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://" + view.getContext().getPackageName() + "/" + imageSource.getResource()); + } + + doLoader(imageSource, false); + } + } + + public void doLoader(boolean rapid) { + doLoader(this.savedImageSource, rapid); + } + + public void doLoader(ImageSource imageSource, boolean rapid) { + if (imageSource.getTile() || view.sRegion != null) { + + // Load the bitmap using tile decoding. + if (rapid) { + view.setRegionDecoderClass(RapidImageRegionDecoder.class); // Assuming this method remains public or becomes public + TilesInitTask task = new TilesInitTask(view, view.getContext(), view.regionDecoderFactory, view.uri); // regionDecoderFactory and uri are public + view.execute(task); + } else { + this.savedImageSource = imageSource; + TilesInitTask task = new TilesInitTask(view, view.getContext(), view.regionDecoderFactory, view.uri); // regionDecoderFactory and uri are public + view.execute(task); + } + } else { + // Load the bitmap as a single image. + SubsamplingScaleImageView.BitmapLoadTask task = new SubsamplingScaleImageView.BitmapLoadTask(view, view.getContext(), view.bitmapDecoderFactory, view.uri, false); // bitmapDecoderFactory and uri are public + view.execute(task); + } + } + + public void setGestureDetector(final Context context) { + // Use the new external listener class directly. + // The methods onSingleTapConfirmed and onDoubleTap will be moved next. + view.detector = new GestureDetector(context, new ImageViewGestureListener(view, context)); + + view.singleDetector = new GestureDetector(context, new GestureDetector.SimpleOnGestureListener() { + @Override + public boolean onSingleTapConfirmed(MotionEvent e) { + view.performClick(); + return true; + } + }); + } + + /** + * Called on first draw when the view has dimensions. Calculates the initial sample size and + * starts async loading of the base layer image - the whole source subsampled as necessary. + */ + public synchronized void initialiseBaseLayer(@NonNull Point maxTileDimensions) { + view.debug("initialiseBaseLayer maxTileDimensions=%dx%d", maxTileDimensions.x, maxTileDimensions.y); + + view.satTemp = new SubsamplingScaleImageView.ScaleAndTranslate(0f, new PointF(0, 0)); // satTemp is public + SubsamplingScaleImageViewDrawHelper.fitToBounds(view, true, view.satTemp); + + // Load double resolution - next level will be split into four tiles and at the center all + // four are required, so don't bother with tiling until the next level 16 tiles are needed. + view.fullImageSampleSize = view.calculateInSampleSize(view.satTemp.scale); // fullImageSampleSize and calculateInSampleSize are public + + if (view.fullImageSampleSize > 1) { + view.fullImageSampleSize /= 2; + } + + if (view.fullImageSampleSize == 1 && view.sRegion == null && SubsamplingScaleImageViewStateHelper.sWidth(view) < maxTileDimensions.x + && SubsamplingScaleImageViewStateHelper.sHeight(view) < maxTileDimensions.y) { // sRegion is public + // Whole image is required at native resolution, and is smaller than the canvas max + // bitmap size. Use BitmapDecoder for better image support. + view.decoder.recycle(); // decoder is public + view.decoder = null; + SubsamplingScaleImageView.BitmapLoadTask task = new SubsamplingScaleImageView.BitmapLoadTask(view, view.getContext(), view.bitmapDecoderFactory, view.uri, false); // bitmapDecoderFactory and uri are public + view.execute(task); + } else { + view.initialiseTileMap(maxTileDimensions); // initialiseTileMap is public + + List baseGrid = view.tileMap.get(view.fullImageSampleSize); // tileMap and fullImageSampleSize are public + + for (SubsamplingScaleImageView.Tile baseTile : baseGrid) { + SubsamplingScaleImageView.TileLoadTask task = new SubsamplingScaleImageView.TileLoadTask(view, view.decoder, baseTile); // TileLoadTask needs to be public + view.execute(task); + } + + view.refreshRequiredTiles(true); // refreshRequiredTiles is public + } + } +} \ No newline at end of file diff --git a/app/src/main/java/me/edgan/redditslide/util/TilesInitTask.java b/app/src/main/java/me/edgan/redditslide/util/TilesInitTask.java index 8c5c22d2b..82c06cb03 100644 --- a/app/src/main/java/me/edgan/redditslide/util/TilesInitTask.java +++ b/app/src/main/java/me/edgan/redditslide/util/TilesInitTask.java @@ -48,13 +48,13 @@ protected int[] doInBackground(Void... params) { } catch (Exception e) { // switch to new decoder cancel(true); - view.doLoader(true); + view.loader.doLoader(true); return null; } int sWidth = dimensions.x; int sHeight = dimensions.y; - int exifOrientation = view.getExifOrientation(context, sourceUri); + int exifOrientation = view.loader.getExifOrientation(context, sourceUri); if (view.sRegion != null) { view.sRegion.left = Math.max(0, view.sRegion.left); @@ -79,9 +79,9 @@ protected void onPostExecute(int[] xyo) { if (xyo != null) { final SubsamplingScaleImageView view = viewRef.get(); if (view != null) { - if (decoder != null && xyo != null && xyo.length == 3) { // Fixed && - view.onTilesInited(decoder, xyo[0], xyo[1], xyo[2]); - } else if (exception != null && view.onImageEventListener != null) { // Fixed && + if (decoder != null && xyo != null && xyo.length == 3) { + view.loader.onTilesInited(decoder, xyo[0], xyo[1], xyo[2]); + } else if (exception != null && view.onImageEventListener != null) { view.onImageEventListener.onImageLoadError(exception); } } diff --git a/app/src/main/java/me/edgan/redditslide/util/TouchEventUtil.java b/app/src/main/java/me/edgan/redditslide/util/TouchEventUtil.java index 3be68166c..6600dec9a 100644 --- a/app/src/main/java/me/edgan/redditslide/util/TouchEventUtil.java +++ b/app/src/main/java/me/edgan/redditslide/util/TouchEventUtil.java @@ -75,10 +75,10 @@ public static boolean handleTouchEventInternal(@NonNull SubsamplingScaleImageVie double previousScale = view.scale; view.scale = Math.min(view.maxScale, (vDistEnd / view.vDistStart) * view.scaleStart); - if (view.scale <= SubsamplingScaleImageViewStateHelper.minScale(view)) { // Use helper + if (view.scale <= SubsamplingScaleImageViewStateHelper.minScale(view)) { // Minimum scale reached so don't pan. Adjust start settings so any expand will zoom in. view.vDistStart = vDistEnd; - view.scaleStart = SubsamplingScaleImageViewStateHelper.minScale(view); // Use helper + view.scaleStart = SubsamplingScaleImageViewStateHelper.minScale(view); view.vCenterStart.set(vCenterEndX, vCenterEndY); view.vTranslateStart.set(view.vTranslate); } else if (view.panEnabled) { @@ -89,10 +89,10 @@ public static boolean handleTouchEventInternal(@NonNull SubsamplingScaleImageVie float vTopNow = vTopStart * (view.scale / view.scaleStart); view.vTranslate.x = vCenterEndX - vLeftNow; view.vTranslate.y = vCenterEndY - vTopNow; - if ((previousScale * SubsamplingScaleImageViewStateHelper.sHeight(view) < view.getHeight() // Use helper - && view.scale * SubsamplingScaleImageViewStateHelper.sHeight(view) >= view.getHeight()) // Use helper - || (previousScale * SubsamplingScaleImageViewStateHelper.sWidth(view) < view.getWidth() // Use helper - && view.scale * SubsamplingScaleImageViewStateHelper.sWidth(view) >= view.getWidth())) { // Use helper + if ((previousScale * SubsamplingScaleImageViewStateHelper.sHeight(view) < view.getHeight() + && view.scale * SubsamplingScaleImageViewStateHelper.sHeight(view) >= view.getHeight()) + || (previousScale * SubsamplingScaleImageViewStateHelper.sWidth(view) < view.getWidth() + && view.scale * SubsamplingScaleImageViewStateHelper.sWidth(view) >= view.getWidth())) { view.fitToBounds(true); view.vCenterStart.set(vCenterEndX, vCenterEndY); view.vTranslateStart.set(view.vTranslate); @@ -105,8 +105,8 @@ public static boolean handleTouchEventInternal(@NonNull SubsamplingScaleImageVie view.vTranslate.y = (view.getHeight() / 2.0f) - (view.scale * view.sRequestedCenter.y); } else { // With no requested center, scale around the image center. - view.vTranslate.x = (view.getWidth() / 2.0f) - (view.scale * (SubsamplingScaleImageViewStateHelper.sWidth(view) / 2.0f)); // Use helper - view.vTranslate.y = (view.getHeight() / 2.0f) - (view.scale * (SubsamplingScaleImageViewStateHelper.sHeight(view) / 2.0f)); // Use helper + view.vTranslate.x = (view.getWidth() / 2.0f) - (view.scale * (SubsamplingScaleImageViewStateHelper.sWidth(view) / 2.0f)); + view.vTranslate.y = (view.getHeight() / 2.0f) - (view.scale * (SubsamplingScaleImageViewStateHelper.sHeight(view) / 2.0f)); } view.fitToBounds(true); @@ -136,7 +136,7 @@ public static boolean handleTouchEventInternal(@NonNull SubsamplingScaleImageVie } double previousScale = view.scale; - view.scale = Math.max(SubsamplingScaleImageViewStateHelper.minScale(view), Math.min(view.maxScale, view.scale * multiplier)); // Use helper + view.scale = Math.max(SubsamplingScaleImageViewStateHelper.minScale(view), Math.min(view.maxScale, view.scale * multiplier)); if (view.panEnabled) { float vLeftStart = view.vCenterStart.x - view.vTranslateStart.x; @@ -146,12 +146,12 @@ public static boolean handleTouchEventInternal(@NonNull SubsamplingScaleImageVie view.vTranslate.x = view.vCenterStart.x - vLeftNow; view.vTranslate.y = view.vCenterStart.y - vTopNow; - if ((previousScale * SubsamplingScaleImageViewStateHelper.sHeight(view) < view.getHeight() // Use helper - && view.scale * SubsamplingScaleImageViewStateHelper.sHeight(view) >= view.getHeight()) // Use helper - || (previousScale * SubsamplingScaleImageViewStateHelper.sWidth(view) < view.getWidth() // Use helper - && view.scale * SubsamplingScaleImageViewStateHelper.sWidth(view) >= view.getWidth())) { // Use helper + if ((previousScale * SubsamplingScaleImageViewStateHelper.sHeight(view) < view.getHeight() + && view.scale * SubsamplingScaleImageViewStateHelper.sHeight(view) >= view.getHeight()) + || (previousScale * SubsamplingScaleImageViewStateHelper.sWidth(view) < view.getWidth() + && view.scale * SubsamplingScaleImageViewStateHelper.sWidth(view) >= view.getWidth())) { view.fitToBounds(true); - view.vCenterStart.set(SubsamplingScaleImageViewStateHelper.sourceToViewCoord(view, view.quickScaleSCenter)); // Use helper + view.vCenterStart.set(SubsamplingScaleImageViewStateHelper.sourceToViewCoord(view, view.quickScaleSCenter)); view.vTranslateStart.set(view.vTranslate); view.scaleStart = view.scale; dist = 0; @@ -162,8 +162,8 @@ public static boolean handleTouchEventInternal(@NonNull SubsamplingScaleImageVie view.vTranslate.y = (view.getHeight() / 2.0f) - (view.scale * view.sRequestedCenter.y); } else { // With no requested center, scale around the image center. - view.vTranslate.x = (view.getWidth() / 2.0f) - (view.scale * (SubsamplingScaleImageViewStateHelper.sWidth(view) / 2.0f)); // Use helper - view.vTranslate.y = (view.getHeight() / 2.0f) - (view.scale * (SubsamplingScaleImageViewStateHelper.sHeight(view) / 2.0f)); // Use helper + view.vTranslate.x = (view.getWidth() / 2.0f) - (view.scale * (SubsamplingScaleImageViewStateHelper.sWidth(view) / 2.0f)); + view.vTranslate.y = (view.getHeight() / 2.0f) - (view.scale * (SubsamplingScaleImageViewStateHelper.sHeight(view) / 2.0f)); } } From bb0291501ebb59c92087e87ee7758fff9c6691ef Mon Sep 17 00:00:00 2001 From: Nathan Grennan Date: Wed, 2 Apr 2025 02:12:20 -0700 Subject: [PATCH 25/99] Broke CommentStateUtil.java out of CommentAdapter.java --- .../redditslide/Adapters/CommentAdapter.java | 618 +----------------- .../redditslide/util/CommentStateUtil.java | 568 ++++++++++++++++ 2 files changed, 583 insertions(+), 603 deletions(-) create mode 100644 app/src/main/java/me/edgan/redditslide/util/CommentStateUtil.java diff --git a/app/src/main/java/me/edgan/redditslide/Adapters/CommentAdapter.java b/app/src/main/java/me/edgan/redditslide/Adapters/CommentAdapter.java index dc5b35efb..2d8c67e56 100644 --- a/app/src/main/java/me/edgan/redditslide/Adapters/CommentAdapter.java +++ b/app/src/main/java/me/edgan/redditslide/Adapters/CommentAdapter.java @@ -23,7 +23,6 @@ import android.view.ViewGroup; import android.view.inputmethod.InputMethodManager; import android.widget.EditText; -import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.RelativeLayout; import android.widget.TextView; @@ -40,7 +39,6 @@ import com.mikepenz.itemanimators.SlideRightAlphaAnimator; import com.nostra13.universalimageloader.utils.DiskCacheUtils; -import me.edgan.redditslide.ActionStates; import me.edgan.redditslide.Authentication; import me.edgan.redditslide.BuildConfig; import me.edgan.redditslide.Constants; @@ -55,21 +53,18 @@ import me.edgan.redditslide.SettingValues; import me.edgan.redditslide.SpoilerRobotoTextView; import me.edgan.redditslide.SubmissionViews.PopulateSubmissionViewHolder; -import me.edgan.redditslide.UserSubscriptions; import me.edgan.redditslide.Views.CommentOverflow; import me.edgan.redditslide.Views.DoEditorActions; import me.edgan.redditslide.Views.PreCachingLayoutManagerComments; import me.edgan.redditslide.Visuals.FontPreferences; import me.edgan.redditslide.Visuals.Palette; -import me.edgan.redditslide.Vote; import me.edgan.redditslide.util.AnimatorUtil; -import me.edgan.redditslide.util.BlendModeUtil; import me.edgan.redditslide.util.DisplayUtil; import me.edgan.redditslide.util.KeyboardUtil; import me.edgan.redditslide.util.LogUtil; import me.edgan.redditslide.util.OnSingleClickListener; import me.edgan.redditslide.util.SubmissionParser; -import me.edgan.redditslide.util.stubs.SimpleTextWatcher; +import me.edgan.redditslide.util.CommentStateUtil; import net.dean.jraw.ApiException; import net.dean.jraw.RedditClient; @@ -79,14 +74,12 @@ import net.dean.jraw.models.CommentNode; import net.dean.jraw.models.Contribution; import net.dean.jraw.models.Submission; -import net.dean.jraw.models.VoteDirection; import java.io.File; import java.io.PrintWriter; import java.io.StringWriter; import java.io.Writer; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -116,14 +109,14 @@ public class CommentAdapter extends RecyclerView.Adapter currentComments; public ArrayList deleted = new ArrayList<>(); RecyclerView listView; - CommentPage mPage; + public CommentPage mPage; int shifted; int toShiftTo; HashSet hidden; - ArrayList hiddenPersons; - ArrayList toCollapse; - private String backedText = ""; - private String currentlyEditingId = ""; + public ArrayList hiddenPersons; + public ArrayList toCollapse; + public String backedText = ""; + public String currentlyEditingId = ""; public SubmissionViewHolder submissionViewHolder; long lastSeen = 0; public ArrayList approved = new ArrayList<>(); @@ -750,7 +743,7 @@ public void onSingleClick(View v) { } AsyncLoadMore currentLoading; - String changedProfile; + public String changedProfile; private void doReplySubmission(RecyclerView.ViewHolder submissionViewHolder) { final View replyArea = submissionViewHolder.itemView.findViewById(R.id.innerSend); @@ -949,7 +942,7 @@ public void setViews( } } - private void setViews(String rawHTML, String subredditName, CommentViewHolder holder) { + public void setViews(String rawHTML, String subredditName, CommentViewHolder holder) { setViews(rawHTML, subredditName, holder.firstTextView, holder.commentOverflow); } @@ -968,7 +961,7 @@ private void setViews( longClickListener); } - int editingPosition; + public int editingPosition; private void collapseAndHide(final View v) { int finalHeight = v.getHeight(); @@ -1012,7 +1005,7 @@ public void onAnimationCancel(Animator animation) { mAnimator.start(); } - private void doShowMenu(final View l) { + public void doShowMenu(final View l) { l.setVisibility(View.VISIBLE); final int widthSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); @@ -1043,7 +1036,7 @@ public void onAnimationCancel(Animator animation) { ValueAnimator mAnimator; - private void expand(final View l) { + public void expand(final View l) { // Made public (was private) l.setVisibility(View.VISIBLE); final int widthSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); @@ -1064,7 +1057,7 @@ private void expand(final View l) { mAnimator.start(); } - private void expandAndSetParams(final View l) { + public void expandAndSetParams(final View l) { // Made public (was private) l.setVisibility(View.VISIBLE); final int widthSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); @@ -1138,7 +1131,7 @@ public void onAnimationCancel(Animator animation) { mAnimator.start(); } - CommentNode currentBaseNode; + public CommentNode currentBaseNode; public void setCommentStateHighlighted( final CommentViewHolder holder, @@ -1146,589 +1139,8 @@ public void setCommentStateHighlighted( final CommentNode baseNode, boolean isReplying, boolean animate) { - if (currentlySelected != null && currentlySelected != holder) { - setCommentStateUnhighlighted(currentlySelected, currentBaseNode, true); - } - - // If a comment is hidden and (Swap long press == true), then a single click will un-hide - // the comment - // and expand to show all children comments - if (SettingValues.swap - && holder.firstTextView.getVisibility() == View.GONE - && !isReplying) { - hiddenPersons.remove(n.getFullName()); - unhideAll(baseNode, holder.getBindingAdapterPosition() + 1); - if (toCollapse.contains(n.getFullName()) && SettingValues.collapseComments) { - setViews( - n.getDataNode().get("body_html").asText(), - submission.getSubredditName(), - holder); - } - CommentAdapterHelper.hideChildrenObject(holder.childrenNumber); - holder.commentOverflow.setVisibility(View.VISIBLE); - toCollapse.remove(n.getFullName()); - } else { - currentlySelected = holder; - currentBaseNode = baseNode; - int color = Palette.getColor(n.getSubredditName()); - currentSelectedItem = n.getFullName(); - currentNode = baseNode; - LayoutInflater inflater = ((Activity) mContext).getLayoutInflater(); - resetMenu(holder.menuArea, false); - final View baseView = - inflater.inflate( - SettingValues.rightHandedCommentMenu - ? R.layout.comment_menu_right_handed - : R.layout.comment_menu, - holder.menuArea); - - if (!isReplying) { - baseView.setVisibility(View.GONE); - if (animate) { - expand(baseView); - } else { - baseView.setVisibility(View.VISIBLE); - final int widthSpec = - View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); - final int heightSpec = - View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); - baseView.measure(widthSpec, heightSpec); - View l2 = - baseView.findViewById(R.id.replyArea) == null - ? baseView.findViewById(R.id.innerSend) - : baseView.findViewById(R.id.replyArea); - final int widthSpec2 = - View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); - final int heightSpec2 = - View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); - l2.measure(widthSpec2, heightSpec2); - ViewGroup.LayoutParams layoutParams = baseView.getLayoutParams(); - layoutParams.height = baseView.getMeasuredHeight() - l2.getMeasuredHeight(); - baseView.setLayoutParams(layoutParams); - } - } - - RecyclerView.LayoutParams params = - (RecyclerView.LayoutParams) holder.itemView.getLayoutParams(); - params.setMargins(0, 0, 0, 0); - holder.itemView.setLayoutParams(params); - - View reply = baseView.findViewById(R.id.reply); - View send = baseView.findViewById(R.id.send); - - final View menu = baseView.findViewById(R.id.menu); - final View replyArea = baseView.findViewById(R.id.replyArea); - - final View more = baseView.findViewById(R.id.more); - final ImageView upvote = baseView.findViewById(R.id.upvote); - final ImageView downvote = baseView.findViewById(R.id.downvote); - View discard = baseView.findViewById(R.id.discard); - final EditText replyLine = baseView.findViewById(R.id.replyLine); - final ImageView mod = baseView.findViewById(R.id.mod); - - final Comment comment = baseNode.getComment(); - if (ActionStates.getVoteDirection(comment) == VoteDirection.UPVOTE) { - BlendModeUtil.tintImageViewAsModulate(upvote, holder.textColorUp); - upvote.setContentDescription( - mContext.getResources().getString(R.string.btn_upvoted)); - } else if (ActionStates.getVoteDirection(comment) == VoteDirection.DOWNVOTE) { - BlendModeUtil.tintImageViewAsModulate(downvote, holder.textColorDown); - downvote.setContentDescription( - mContext.getResources().getString(R.string.btn_downvoted)); - } else { - downvote.clearColorFilter(); - downvote.setContentDescription( - mContext.getResources().getString(R.string.btn_downvote)); - upvote.clearColorFilter(); - upvote.setContentDescription( - mContext.getResources().getString(R.string.btn_upvote)); - } - - try { - if (UserSubscriptions.modOf.contains(submission.getSubredditName())) { - // todo - mod.setVisibility(View.GONE); - } else { - mod.setVisibility(View.GONE); - } - } catch (Exception e) { - Log.d(LogUtil.getTag(), "Error loading mod " + e.toString()); - } - - if (UserSubscriptions.modOf != null - && UserSubscriptions.modOf.contains( - submission.getSubredditName().toLowerCase(Locale.ENGLISH))) { - mod.setVisibility(View.VISIBLE); - final Map reports = comment.getUserReports(); - final Map reports2 = comment.getModeratorReports(); - if (reports.size() + reports2.size() > 0) { - BlendModeUtil.tintImageViewAsSrcAtop( - mod, ContextCompat.getColor(mContext, R.color.md_red_300)); - } else { - BlendModeUtil.tintImageViewAsSrcAtop(mod, Color.WHITE); - } - mod.setOnClickListener( - new OnSingleClickListener() { - @Override - public void onSingleClick(View v) { - CommentAdapterHelper.showModBottomSheet( - CommentAdapter.this, - mContext, - baseNode, - comment, - holder, - reports, - reports2); - } - }); - } else { - mod.setVisibility(View.GONE); - } - - final ImageView edit = baseView.findViewById(R.id.edit); - if (Authentication.name != null - && Authentication.name - .toLowerCase(Locale.ENGLISH) - .equals(comment.getAuthor().toLowerCase(Locale.ENGLISH)) - && Authentication.didOnline) { - edit.setOnClickListener( - new OnSingleClickListener() { - @Override - public void onSingleClick(View v) { - CommentAdapterHelper.doCommentEdit( - CommentAdapter.this, - mContext, - fm, - baseNode, - baseNode.isTopLevel() - ? submission.getSelftext() - : baseNode.getParent().getComment().getBody(), - holder); - } - }); - } else { - edit.setVisibility(View.GONE); - } - - final ImageView delete = baseView.findViewById(R.id.delete); - if (Authentication.name != null - && Authentication.name - .toLowerCase(Locale.ENGLISH) - .equals(comment.getAuthor().toLowerCase(Locale.ENGLISH)) - && Authentication.didOnline) { - delete.setOnClickListener( - new OnSingleClickListener() { - @Override - public void onSingleClick(View v) { - CommentAdapterHelper.deleteComment( - CommentAdapter.this, mContext, baseNode, holder); - } - }); - } else { - delete.setVisibility(View.GONE); - } - - if (Authentication.isLoggedIn - && !submission.isArchived() - && !submission.isLocked() - && !(comment.getDataNode().has("locked") - && comment.getDataNode().get("locked").asBoolean()) - && !deleted.contains(n.getFullName()) - && !comment.getAuthor().equals("[deleted]") - && Authentication.didOnline) { - if (isReplying) { - baseView.setVisibility(View.VISIBLE); - - final int widthSpec = - View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); - final int heightSpec = - View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); - baseView.measure(widthSpec, heightSpec); - - View l2 = - baseView.findViewById(R.id.replyArea) == null - ? baseView.findViewById(R.id.innerSend) - : baseView.findViewById(R.id.replyArea); - final int widthSpec2 = - View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); - final int heightSpec2 = - View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); - l2.measure(widthSpec2, heightSpec2); - RelativeLayout.LayoutParams params2 = - (RelativeLayout.LayoutParams) baseView.getLayoutParams(); - params2.height = RelativeLayout.LayoutParams.WRAP_CONTENT; - params2.addRule(RelativeLayout.BELOW, R.id.commentOverflow); - baseView.setLayoutParams(params2); - replyArea.setVisibility(View.VISIBLE); - menu.setVisibility(View.GONE); - currentlyEditing = replyLine; - currentlyEditing.setOnFocusChangeListener( - new View.OnFocusChangeListener() { - @Override - public void onFocusChange(View v, boolean hasFocus) { - if (hasFocus) { - mPage.fastScroll.setVisibility(View.GONE); - if (mPage.fab != null) { - mPage.fab.setVisibility(View.GONE); - } - mPage.overrideFab = true; - } else if (SettingValues.fastscroll) { - mPage.fastScroll.setVisibility(View.VISIBLE); - if (mPage.fab != null) { - mPage.fab.setVisibility(View.VISIBLE); - } - mPage.overrideFab = false; - } - } - }); - final TextView profile = baseView.findViewById(R.id.profile); - changedProfile = Authentication.name; - profile.setText("/u/" + changedProfile); - profile.setOnClickListener( - new View.OnClickListener() { - @Override - public void onClick(View v) { - final HashMap accounts = new HashMap<>(); - - for (String s : - Authentication.authentication.getStringSet( - "accounts", new HashSet())) { - if (s.contains(":")) { - accounts.put(s.split(":")[0], s.split(":")[1]); - } else { - accounts.put(s, ""); - } - } - final ArrayList keys = - new ArrayList<>(accounts.keySet()); - final int i = keys.indexOf(changedProfile); - - new AlertDialog.Builder(mContext) - .setTitle(R.string.sorting_choose) - .setSingleChoiceItems( - keys.toArray(new String[0]), - i, - (dialog, which) -> { - changedProfile = keys.get(which); - profile.setText("/u/" + changedProfile); - }) - .setNegativeButton(R.string.btn_cancel, null) - .show(); - } - }); - replyLine.requestFocus(); - KeyboardUtil.toggleKeyboard( - mContext, - InputMethodManager.SHOW_FORCED, - InputMethodManager.HIDE_IMPLICIT_ONLY); - - currentlyEditingId = n.getFullName(); - replyLine.setText(backedText); - replyLine.addTextChangedListener( - new SimpleTextWatcher() { - @Override - public void onTextChanged( - CharSequence s, int start, int before, int count) { - backedText = s.toString(); - } - }); - editingPosition = holder.getBindingAdapterPosition(); - } - reply.setOnClickListener( - new OnSingleClickListener() { - @Override - public void onSingleClick(View v) { - expandAndSetParams(baseView); - - // If the base theme is Light or Sepia, tint the Editor actions to - // be white - if (SettingValues.currentTheme == 1 - || SettingValues.currentTheme == 5) { - final ImageView saveDraft = - (ImageView) replyArea.findViewById(R.id.savedraft); - final ImageView draft = - (ImageView) replyArea.findViewById(R.id.draft); - final ImageView imagerep = - (ImageView) replyArea.findViewById(R.id.imagerep); - final ImageView link = - (ImageView) replyArea.findViewById(R.id.link); - final ImageView bold = - (ImageView) replyArea.findViewById(R.id.bold); - final ImageView italics = - (ImageView) replyArea.findViewById(R.id.italics); - final ImageView bulletlist = - (ImageView) replyArea.findViewById(R.id.bulletlist); - final ImageView numlist = - (ImageView) replyArea.findViewById(R.id.numlist); - final ImageView draw = - (ImageView) replyArea.findViewById(R.id.draw); - final ImageView quote = - (ImageView) replyArea.findViewById(R.id.quote); - final ImageView size = - (ImageView) replyArea.findViewById(R.id.size); - final ImageView strike = - (ImageView) replyArea.findViewById(R.id.strike); - final ImageView author = - (ImageView) replyArea.findViewById(R.id.author); - final ImageView spoiler = - (ImageView) replyArea.findViewById(R.id.spoiler); - final List imageViewSet = - Arrays.asList( - saveDraft, - draft, - imagerep, - link, - bold, - italics, - bulletlist, - numlist, - draw, - quote, - size, - strike, - author, - spoiler); - BlendModeUtil.tintImageViewsAsSrcAtop( - imageViewSet, Color.WHITE); - BlendModeUtil.tintDrawableAsSrcIn( - replyLine.getBackground(), Color.WHITE); - } - - replyArea.setVisibility(View.VISIBLE); - menu.setVisibility(View.GONE); - currentlyEditing = replyLine; - DoEditorActions.doActions( - currentlyEditing, - replyArea, - fm, - (Activity) mContext, - comment.getBody(), - getParents(baseNode)); - currentlyEditing.setOnFocusChangeListener( - new View.OnFocusChangeListener() { - @Override - public void onFocusChange(View v, boolean hasFocus) { - if (hasFocus) { - mPage.fastScroll.setVisibility(View.GONE); - if (mPage.fab != null) - mPage.fab.setVisibility(View.GONE); - mPage.overrideFab = true; - } else if (SettingValues.fastscroll) { - mPage.fastScroll.setVisibility(View.VISIBLE); - if (mPage.fab != null) - mPage.fab.setVisibility(View.VISIBLE); - mPage.overrideFab = false; - } - } - }); - final TextView profile = baseView.findViewById(R.id.profile); - changedProfile = Authentication.name; - profile.setText("/u/" + changedProfile); - profile.setOnClickListener( - new View.OnClickListener() { - @Override - public void onClick(View v) { - final HashMap accounts = - new HashMap<>(); - - for (String s : - Authentication.authentication.getStringSet( - "accounts", - new HashSet())) { - if (s.contains(":")) { - accounts.put( - s.split(":")[0], s.split(":")[1]); - } else { - accounts.put(s, ""); - } - } - final ArrayList keys = - new ArrayList<>(accounts.keySet()); - final int i = keys.indexOf(changedProfile); - - new AlertDialog.Builder(mContext) - .setTitle(R.string.sorting_choose) - .setSingleChoiceItems( - keys.toArray(new String[0]), - i, - (dialog, which) -> { - changedProfile = - keys.get(which); - profile.setText( - "/u/" + changedProfile); - }) - .setNegativeButton( - R.string.btn_cancel, null) - .show(); - } - }); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { - replyLine.setOnFocusChangeListener( - (view, b) -> { - if (b) { - view.postDelayed( - () -> { - if (!view.hasFocus()) - view.requestFocus(); - }, - 100); - } - }); - } - replyLine.requestFocus(); // TODO: Not working when called a second - // time - KeyboardUtil.toggleKeyboard( - mContext, - InputMethodManager.SHOW_FORCED, - InputMethodManager.HIDE_IMPLICIT_ONLY); - - currentlyEditingId = n.getFullName(); - replyLine.addTextChangedListener( - new SimpleTextWatcher() { - @Override - public void onTextChanged( - CharSequence s, - int start, - int before, - int count) { - backedText = s.toString(); - } - }); - editingPosition = holder.getBindingAdapterPosition(); - } - }); - send.setOnClickListener( - new OnSingleClickListener() { - @Override - public void onSingleClick(View v) { - currentlyEditingId = ""; - backedText = ""; - - doShowMenu(baseView); - if (SettingValues.fastscroll) { - mPage.fastScroll.setVisibility(View.VISIBLE); - if (mPage.fab != null) mPage.fab.setVisibility(View.VISIBLE); - mPage.overrideFab = false; - } - dataSet.refreshLayout.setRefreshing(true); - if (currentlyEditing != null) { - String text = currentlyEditing.getText().toString(); - new ReplyTaskComment(n, baseNode, holder, changedProfile) - .execute(text); - currentlyEditing = null; - editingPosition = -1; - } - // Hide soft keyboard - View view = - ((Activity) mContext).findViewById(android.R.id.content); - if (view != null) { - KeyboardUtil.hideKeyboard(mContext, view.getWindowToken(), 0); - } - } - }); - discard.setOnClickListener( - new OnSingleClickListener() { - @Override - public void onSingleClick(View v) { - currentlyEditing = null; - editingPosition = -1; - currentlyEditingId = ""; - backedText = ""; - mPage.overrideFab = false; - View view = - ((Activity) mContext).findViewById(android.R.id.content); - if (view != null) { - KeyboardUtil.hideKeyboard(mContext, view.getWindowToken(), 0); - } - doShowMenu(baseView); - } - }); - } else { - if (reply.getVisibility() == View.VISIBLE) { - reply.setVisibility(View.GONE); - } - if ((submission.isArchived() - || deleted.contains(n.getFullName()) - || comment.getAuthor().equals("[deleted]")) - && Authentication.isLoggedIn - && Authentication.didOnline - && upvote.getVisibility() == View.VISIBLE) { - upvote.setVisibility(View.GONE); - } - if ((submission.isArchived() - || deleted.contains(n.getFullName()) - || comment.getAuthor().equals("[deleted]")) - && Authentication.isLoggedIn - && Authentication.didOnline - && downvote.getVisibility() == View.VISIBLE) { - downvote.setVisibility(View.GONE); - } - } - - more.setOnClickListener( - new OnSingleClickListener() { - @Override - public void onSingleClick(View v) { - CommentAdapterHelper.showOverflowBottomSheet( - CommentAdapter.this, mContext, holder, baseNode); - } - }); - upvote.setOnClickListener( - new OnSingleClickListener() { - - @Override - public void onSingleClick(View v) { - setCommentStateUnhighlighted(holder, comment, baseNode, true); - if (ActionStates.getVoteDirection(comment) == VoteDirection.UPVOTE) { - new Vote(v, mContext).execute(n); - ActionStates.setVoteDirection(comment, VoteDirection.NO_VOTE); - doScoreText(holder, n, CommentAdapter.this); - upvote.clearColorFilter(); - } else { - new Vote(true, v, mContext).execute(n); - ActionStates.setVoteDirection(comment, VoteDirection.UPVOTE); - downvote.clearColorFilter(); // reset colour - doScoreText(holder, n, CommentAdapter.this); - BlendModeUtil.tintImageViewAsModulate(upvote, holder.textColorUp); - } - } - }); - downvote.setOnClickListener( - new OnSingleClickListener() { - - @Override - public void onSingleClick(View v) { - setCommentStateUnhighlighted(holder, comment, baseNode, true); - if (ActionStates.getVoteDirection(comment) == VoteDirection.DOWNVOTE) { - new Vote(v, mContext).execute(n); - ActionStates.setVoteDirection(comment, VoteDirection.NO_VOTE); - doScoreText(holder, n, CommentAdapter.this); - downvote.clearColorFilter(); - - } else { - new Vote(false, v, mContext).execute(n); - ActionStates.setVoteDirection(comment, VoteDirection.DOWNVOTE); - upvote.clearColorFilter(); // reset colour - doScoreText(holder, n, CommentAdapter.this); - BlendModeUtil.tintImageViewAsModulate( - downvote, holder.textColorDown); - } - } - }); - menu.setBackgroundColor(color); - replyArea.setBackgroundColor(color); - - if (!isReplying) { - menu.setVisibility(View.VISIBLE); - replyArea.setVisibility(View.GONE); - } - - holder.itemView - .findViewById(R.id.background) - .setBackgroundColor( - Color.argb( - 50, Color.red(color), Color.green(color), Color.blue(color))); - } + // Call the extracted static method + CommentStateUtil.handleSetCommentStateHighlighted(this, holder, n, baseNode, isReplying, animate); } public void doHighlighted( diff --git a/app/src/main/java/me/edgan/redditslide/util/CommentStateUtil.java b/app/src/main/java/me/edgan/redditslide/util/CommentStateUtil.java new file mode 100644 index 000000000..8fd9843a3 --- /dev/null +++ b/app/src/main/java/me/edgan/redditslide/util/CommentStateUtil.java @@ -0,0 +1,568 @@ +package me.edgan.redditslide.util; + +import android.app.Activity; +import android.graphics.Color; +import android.os.Build; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.inputmethod.InputMethodManager; +import android.widget.EditText; +import android.widget.ImageView; +import android.widget.RelativeLayout; +import android.widget.TextView; +import androidx.appcompat.app.AlertDialog; +import androidx.core.content.ContextCompat; +import androidx.recyclerview.widget.RecyclerView; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import me.edgan.redditslide.ActionStates; +import me.edgan.redditslide.Adapters.CommentAdapter; +import me.edgan.redditslide.Adapters.CommentViewHolder; +import me.edgan.redditslide.Authentication; +import me.edgan.redditslide.R; +import me.edgan.redditslide.SettingValues; +import me.edgan.redditslide.UserSubscriptions; +import me.edgan.redditslide.Views.DoEditorActions; +import me.edgan.redditslide.Visuals.Palette; +import me.edgan.redditslide.Vote; +import me.edgan.redditslide.util.stubs.SimpleTextWatcher; +import net.dean.jraw.models.Comment; +import net.dean.jraw.models.CommentNode; +import net.dean.jraw.models.VoteDirection; + +// Added imports based on usage in the method +import me.edgan.redditslide.Adapters.CommentAdapterHelper; + +public class CommentStateUtil { + + /** + * Handles the logic for setting the highlighted state of a comment view. + * Extracted from CommentAdapter.setCommentStateHighlighted. + */ + public static void handleSetCommentStateHighlighted( + final CommentAdapter adapter, + final CommentViewHolder holder, + final Comment n, + final CommentNode baseNode, + boolean isReplying, + boolean animate) { + + if (adapter.currentlySelected != null && adapter.currentlySelected != holder) { + adapter.setCommentStateUnhighlighted(adapter.currentlySelected, adapter.currentBaseNode, true); + } + + // If a comment is hidden and (Swap long press == true), then a single click will un-hide + // the comment and expand to show all children comments + if (SettingValues.swap && holder.firstTextView.getVisibility() == View.GONE && !isReplying) { + adapter.hiddenPersons.remove(n.getFullName()); + adapter.unhideAll(baseNode, holder.getBindingAdapterPosition() + 1); + if (adapter.toCollapse.contains(n.getFullName()) && SettingValues.collapseComments) { + adapter.setViews(n.getDataNode().get("body_html").asText(), adapter.submission.getSubredditName(), holder); + } + CommentAdapterHelper.hideChildrenObject(holder.childrenNumber); + holder.commentOverflow.setVisibility(View.VISIBLE); + adapter.toCollapse.remove(n.getFullName()); + } else { + adapter.currentlySelected = holder; + adapter.currentBaseNode = baseNode; + int color = Palette.getColor(n.getSubredditName()); + adapter.currentSelectedItem = n.getFullName(); + adapter.currentNode = baseNode; + LayoutInflater inflater = ((Activity) adapter.mContext).getLayoutInflater(); + adapter.resetMenu(holder.menuArea, false); + final View baseView = inflater.inflate(SettingValues.rightHandedCommentMenu ? R.layout.comment_menu_right_handed : R.layout.comment_menu, holder.menuArea); + + if (!isReplying) { + baseView.setVisibility(View.GONE); + if (animate) { + adapter.expand(baseView); + } else { + baseView.setVisibility(View.VISIBLE); + final int widthSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); + final int heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); + baseView.measure(widthSpec, heightSpec); + View l2 = baseView.findViewById(R.id.replyArea) == null ? baseView.findViewById(R.id.innerSend) : baseView.findViewById(R.id.replyArea); + final int widthSpec2 = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); + final int heightSpec2 = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); + l2.measure(widthSpec2, heightSpec2); + ViewGroup.LayoutParams layoutParams = baseView.getLayoutParams(); + layoutParams.height = baseView.getMeasuredHeight() - l2.getMeasuredHeight(); + baseView.setLayoutParams(layoutParams); + } + } + + RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) holder.itemView.getLayoutParams(); + params.setMargins(0, 0, 0, 0); + holder.itemView.setLayoutParams(params); + + View reply = baseView.findViewById(R.id.reply); + View send = baseView.findViewById(R.id.send); + + final View menu = baseView.findViewById(R.id.menu); + final View replyArea = baseView.findViewById(R.id.replyArea); + + final View more = baseView.findViewById(R.id.more); + final ImageView upvote = baseView.findViewById(R.id.upvote); + final ImageView downvote = baseView.findViewById(R.id.downvote); + View discard = baseView.findViewById(R.id.discard); + final EditText replyLine = baseView.findViewById(R.id.replyLine); + final ImageView mod = baseView.findViewById(R.id.mod); + + final Comment comment = baseNode.getComment(); + if (ActionStates.getVoteDirection(comment) == VoteDirection.UPVOTE) { + BlendModeUtil.tintImageViewAsModulate(upvote, holder.textColorUp); + upvote.setContentDescription(adapter.mContext.getResources().getString(R.string.btn_upvoted)); + } else if (ActionStates.getVoteDirection(comment) == VoteDirection.DOWNVOTE) { + BlendModeUtil.tintImageViewAsModulate(downvote, holder.textColorDown); + downvote.setContentDescription(adapter.mContext.getResources().getString(R.string.btn_downvoted)); + } else { + downvote.clearColorFilter(); + downvote.setContentDescription(adapter.mContext.getResources().getString(R.string.btn_downvote)); + upvote.clearColorFilter(); + upvote.setContentDescription(adapter.mContext.getResources().getString(R.string.btn_upvote)); + } + + try { + mod.setVisibility(View.GONE); + } catch (Exception e) { + Log.d(LogUtil.getTag(), "Error loading mod " + e.toString()); + } + + if (UserSubscriptions.modOf != null && UserSubscriptions.modOf.contains(adapter.submission.getSubredditName().toLowerCase(Locale.ENGLISH))) { + mod.setVisibility(View.VISIBLE); + final Map reports = comment.getUserReports(); + final Map reports2 = comment.getModeratorReports(); + if (reports.size() + reports2.size() > 0) { + BlendModeUtil.tintImageViewAsSrcAtop(mod, ContextCompat.getColor(adapter.mContext, R.color.md_red_300)); + } else { + BlendModeUtil.tintImageViewAsSrcAtop(mod, Color.WHITE); + } + mod.setOnClickListener( + new OnSingleClickListener() { + @Override + public void onSingleClick(View v) { + CommentAdapterHelper.showModBottomSheet( + adapter, + adapter.mContext, + baseNode, + comment, + holder, + reports, + reports2); + } + }); + } else { + mod.setVisibility(View.GONE); + } + + final ImageView edit = baseView.findViewById(R.id.edit); + if (Authentication.name != null && Authentication.name.toLowerCase(Locale.ENGLISH).equals(comment.getAuthor().toLowerCase(Locale.ENGLISH)) && Authentication.didOnline) { + edit.setOnClickListener( + new OnSingleClickListener() { + @Override + public void onSingleClick(View v) { + CommentAdapterHelper.doCommentEdit( + adapter, + adapter.mContext, + adapter.fm, + baseNode, + baseNode.isTopLevel() + ? adapter.submission.getSelftext() + : baseNode.getParent().getComment().getBody(), + holder); + } + }); + } else { + edit.setVisibility(View.GONE); + } + + final ImageView delete = baseView.findViewById(R.id.delete); + if (Authentication.name != null && Authentication.name.toLowerCase(Locale.ENGLISH).equals(comment.getAuthor().toLowerCase(Locale.ENGLISH)) && Authentication.didOnline) { + delete.setOnClickListener( + new OnSingleClickListener() { + @Override + public void onSingleClick(View v) { + CommentAdapterHelper.deleteComment(adapter, adapter.mContext, baseNode, holder); + } + }); + } else { + delete.setVisibility(View.GONE); + } + + if (Authentication.isLoggedIn + && !adapter.submission.isArchived() + && !adapter.submission.isLocked() + && !(comment.getDataNode().has("locked") + && comment.getDataNode().get("locked").asBoolean()) + && !adapter.deleted.contains(n.getFullName()) + && !comment.getAuthor().equals("[deleted]") + && Authentication.didOnline) { + if (isReplying) { + baseView.setVisibility(View.VISIBLE); + + final int widthSpec = + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); + final int heightSpec = + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); + baseView.measure(widthSpec, heightSpec); + + View l2 = + baseView.findViewById(R.id.replyArea) == null + ? baseView.findViewById(R.id.innerSend) + : baseView.findViewById(R.id.replyArea); + final int widthSpec2 = + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); + final int heightSpec2 = + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); + l2.measure(widthSpec2, heightSpec2); + RelativeLayout.LayoutParams params2 = (RelativeLayout.LayoutParams) baseView.getLayoutParams(); + params2.height = RelativeLayout.LayoutParams.WRAP_CONTENT; + params2.addRule(RelativeLayout.BELOW, R.id.commentOverflow); + baseView.setLayoutParams(params2); + replyArea.setVisibility(View.VISIBLE); + menu.setVisibility(View.GONE); + adapter.currentlyEditing = replyLine; + adapter.currentlyEditing.setOnFocusChangeListener( + new View.OnFocusChangeListener() { + @Override + public void onFocusChange(View v, boolean hasFocus) { + if (hasFocus) { + adapter.mPage.fastScroll.setVisibility(View.GONE); + if (adapter.mPage.fab != null) { + adapter.mPage.fab.setVisibility(View.GONE); + } + adapter.mPage.overrideFab = true; + } else if (SettingValues.fastscroll) { + adapter.mPage.fastScroll.setVisibility(View.VISIBLE); + if (adapter.mPage.fab != null) { + adapter.mPage.fab.setVisibility(View.VISIBLE); + } + adapter.mPage.overrideFab = false; + } + } + }); + final TextView profile = baseView.findViewById(R.id.profile); + adapter.changedProfile = Authentication.name; + profile.setText("/u/" + adapter.changedProfile); + profile.setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View v) { + final HashMap accounts = new HashMap<>(); + + for (String s : + Authentication.authentication.getStringSet("accounts", new HashSet())) { + if (s.contains(":")) { + accounts.put(s.split(":")[0], s.split(":")[1]); + } else { + accounts.put(s, ""); + } + } + final ArrayList keys = new ArrayList<>(accounts.keySet()); + final int i = keys.indexOf(adapter.changedProfile); + + new AlertDialog.Builder(adapter.mContext) + .setTitle(R.string.sorting_choose) + .setSingleChoiceItems( + keys.toArray(new String[0]), + i, + (dialog, which) -> { + adapter.changedProfile = keys.get(which); + profile.setText("/u/" + adapter.changedProfile); + }) + .setNegativeButton(R.string.btn_cancel, null) + .show(); + } + }); + replyLine.requestFocus(); + KeyboardUtil.toggleKeyboard(adapter.mContext, InputMethodManager.SHOW_FORCED, InputMethodManager.HIDE_IMPLICIT_ONLY); + + adapter.currentlyEditingId = n.getFullName(); + replyLine.setText(adapter.backedText); + replyLine.addTextChangedListener( + new SimpleTextWatcher() { + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + adapter.backedText = s.toString(); + } + }); + adapter.editingPosition = holder.getBindingAdapterPosition(); + } + reply.setOnClickListener( + new OnSingleClickListener() { + @Override + public void onSingleClick(View v) { + adapter.expandAndSetParams(baseView); + + // If the base theme is Light or Sepia, tint the Editor actions to be white + if (SettingValues.currentTheme == 1 + || SettingValues.currentTheme == 5) { + final ImageView saveDraft = (ImageView) replyArea.findViewById(R.id.savedraft); + final ImageView draft = (ImageView) replyArea.findViewById(R.id.draft); + final ImageView imagerep = (ImageView) replyArea.findViewById(R.id.imagerep); + final ImageView link = (ImageView) replyArea.findViewById(R.id.link); + final ImageView bold = (ImageView) replyArea.findViewById(R.id.bold); + final ImageView italics = (ImageView) replyArea.findViewById(R.id.italics); + final ImageView bulletlist = (ImageView) replyArea.findViewById(R.id.bulletlist); + final ImageView numlist = (ImageView) replyArea.findViewById(R.id.numlist); + final ImageView draw = (ImageView) replyArea.findViewById(R.id.draw); + final ImageView quote = (ImageView) replyArea.findViewById(R.id.quote); + final ImageView size = (ImageView) replyArea.findViewById(R.id.size); + final ImageView strike = (ImageView) replyArea.findViewById(R.id.strike); + final ImageView author = (ImageView) replyArea.findViewById(R.id.author); + final ImageView spoiler = (ImageView) replyArea.findViewById(R.id.spoiler); + final List imageViewSet = + Arrays.asList( + saveDraft, + draft, + imagerep, + link, + bold, + italics, + bulletlist, + numlist, + draw, + quote, + size, + strike, + author, + spoiler + ); + BlendModeUtil.tintImageViewsAsSrcAtop(imageViewSet, Color.WHITE); + BlendModeUtil.tintDrawableAsSrcIn(replyLine.getBackground(), Color.WHITE); + } + + replyArea.setVisibility(View.VISIBLE); + menu.setVisibility(View.GONE); + adapter.currentlyEditing = replyLine; + DoEditorActions.doActions( + adapter.currentlyEditing, + replyArea, + adapter.fm, + (Activity) adapter.mContext, + comment.getBody(), + adapter.getParents(baseNode) + ); + adapter.currentlyEditing.setOnFocusChangeListener( + new View.OnFocusChangeListener() { + @Override + public void onFocusChange(View v, boolean hasFocus) { + if (hasFocus) { + adapter.mPage.fastScroll.setVisibility(View.GONE); + if (adapter.mPage.fab != null) { + adapter.mPage.fab.setVisibility(View.GONE); + } + adapter.mPage.overrideFab = true; + } else if (SettingValues.fastscroll) { + adapter.mPage.fastScroll.setVisibility(View.VISIBLE); + if (adapter.mPage.fab != null) { + adapter.mPage.fab.setVisibility(View.VISIBLE); + } + adapter.mPage.overrideFab = false; + } + } + }); + final TextView profile = baseView.findViewById(R.id.profile); + adapter.changedProfile = Authentication.name; // Static field access + profile.setText("/u/" + adapter.changedProfile); + profile.setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View v) { + final HashMap accounts = + new HashMap<>(); + + for (String s : Authentication.authentication.getStringSet("accounts", new HashSet())) { + if (s.contains(":")) { + accounts.put(s.split(":")[0], s.split(":")[1]); + } else { + accounts.put(s, ""); + } + } + final ArrayList keys = new ArrayList<>(accounts.keySet()); + final int i = keys.indexOf(adapter.changedProfile); + + new AlertDialog.Builder(adapter.mContext) + .setTitle(R.string.sorting_choose) + .setSingleChoiceItems( + keys.toArray(new String[0]), + i, + (dialog, which) -> { + adapter.changedProfile = keys.get(which); + profile.setText("/u/" + adapter.changedProfile); + }) + .setNegativeButton( + R.string.btn_cancel, null) + .show(); + } + }); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) { + replyLine.setOnFocusChangeListener( + (view, b) -> { + if (b) { + view.postDelayed( + () -> { + if (!view.hasFocus()) { + view.requestFocus(); + } + }, + 100 + ); + } + }); + } + replyLine.requestFocus(); // TODO: Not working when called a second time + // time + KeyboardUtil.toggleKeyboard(adapter.mContext, InputMethodManager.SHOW_FORCED, InputMethodManager.HIDE_IMPLICIT_ONLY); + + adapter.currentlyEditingId = n.getFullName(); + replyLine.addTextChangedListener( + new SimpleTextWatcher() { + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + adapter.backedText = s.toString(); + } + }); + adapter.editingPosition = holder.getBindingAdapterPosition(); + } + }); + send.setOnClickListener( + new OnSingleClickListener() { + @Override + public void onSingleClick(View v) { + adapter.currentlyEditingId = ""; + adapter.backedText = ""; + + adapter.doShowMenu(baseView); + + if (SettingValues.fastscroll) { + adapter.mPage.fastScroll.setVisibility(View.VISIBLE); + if (adapter.mPage.fab != null) adapter.mPage.fab.setVisibility(View.VISIBLE); + adapter.mPage.overrideFab = false; + } + + adapter.dataSet.refreshLayout.setRefreshing(true); + + if (adapter.currentlyEditing != null) { + String text = adapter.currentlyEditing.getText().toString(); + // Instantiate inner class via adapter instance + adapter.new ReplyTaskComment(n, baseNode, holder, adapter.changedProfile).execute(text); + adapter.currentlyEditing = null; + adapter.editingPosition = -1; + } + + // Hide soft keyboard + View view = ((Activity) adapter.mContext).findViewById(android.R.id.content); + + if (view != null) { + KeyboardUtil.hideKeyboard(adapter.mContext, view.getWindowToken(), 0); + } + } + }); + discard.setOnClickListener( + new OnSingleClickListener() { + @Override + public void onSingleClick(View v) { + adapter.currentlyEditing = null; + adapter.editingPosition = -1; + adapter.currentlyEditingId = ""; + adapter.backedText = ""; + adapter.mPage.overrideFab = false; + View view = ((Activity) adapter.mContext).findViewById(android.R.id.content); + + if (view != null) { + KeyboardUtil.hideKeyboard(adapter.mContext, view.getWindowToken(), 0); + } + + adapter.doShowMenu(baseView); + } + }); + } else { + if (reply.getVisibility() == View.VISIBLE) { + reply.setVisibility(View.GONE); + } + if ((adapter.submission.isArchived() + || adapter.deleted.contains(n.getFullName()) + || comment.getAuthor().equals("[deleted]")) + && Authentication.isLoggedIn + && Authentication.didOnline + && upvote.getVisibility() == View.VISIBLE) { + upvote.setVisibility(View.GONE); + } + if ((adapter.submission.isArchived() + || adapter.deleted.contains(n.getFullName()) + || comment.getAuthor().equals("[deleted]")) + && Authentication.isLoggedIn + && Authentication.didOnline + && downvote.getVisibility() == View.VISIBLE) { + downvote.setVisibility(View.GONE); + } + } + + more.setOnClickListener( + new OnSingleClickListener() { + @Override + public void onSingleClick(View v) { + CommentAdapterHelper.showOverflowBottomSheet(adapter, adapter.mContext, holder, baseNode); + } + }); + upvote.setOnClickListener( + new OnSingleClickListener() { + @Override + public void onSingleClick(View v) { + adapter.setCommentStateUnhighlighted(holder, comment, baseNode, true); + if (ActionStates.getVoteDirection(comment) == VoteDirection.UPVOTE) { + new Vote(v, adapter.mContext).execute(n); + ActionStates.setVoteDirection(comment, VoteDirection.NO_VOTE); + adapter.doScoreText(holder, n, adapter); + upvote.clearColorFilter(); + } else { + new Vote(true, v, adapter.mContext).execute(n); + ActionStates.setVoteDirection(comment, VoteDirection.UPVOTE); + downvote.clearColorFilter(); // reset colour + adapter.doScoreText(holder, n, adapter); + BlendModeUtil.tintImageViewAsModulate(upvote, holder.textColorUp); + } + } + }); + downvote.setOnClickListener( + new OnSingleClickListener() { + + @Override + public void onSingleClick(View v) { + adapter.setCommentStateUnhighlighted(holder, comment, baseNode, true); + if (ActionStates.getVoteDirection(comment) == VoteDirection.DOWNVOTE) { + new Vote(v, adapter.mContext).execute(n); + ActionStates.setVoteDirection(comment, VoteDirection.NO_VOTE); + adapter.doScoreText(holder, n, adapter); + downvote.clearColorFilter(); + } else { + new Vote(false, v, adapter.mContext).execute(n); + ActionStates.setVoteDirection(comment, VoteDirection.DOWNVOTE); + upvote.clearColorFilter(); // reset colour + adapter.doScoreText(holder, n, adapter); + BlendModeUtil.tintImageViewAsModulate(downvote, holder.textColorDown); + } + } + }); + menu.setBackgroundColor(color); + replyArea.setBackgroundColor(color); + + if (!isReplying) { + menu.setVisibility(View.VISIBLE); + replyArea.setVisibility(View.GONE); + } + + holder.itemView + .findViewById(R.id.background) + .setBackgroundColor(Color.argb(50, Color.red(color), Color.green(color), Color.blue(color))); + } + } +} \ No newline at end of file From c4e17607500c58e884a1598f6551ce379381ab8a Mon Sep 17 00:00:00 2001 From: Nathan Grennan Date: Wed, 2 Apr 2025 02:43:56 -0700 Subject: [PATCH 26/99] Broke AsyncLoadMoreTask.java out of CommentAdapter.java --- .../redditslide/Adapters/CommentAdapter.java | 211 +--------------- .../redditslide/util/AsyncLoadMoreTask.java | 238 ++++++++++++++++++ 2 files changed, 248 insertions(+), 201 deletions(-) create mode 100644 app/src/main/java/me/edgan/redditslide/util/AsyncLoadMoreTask.java diff --git a/app/src/main/java/me/edgan/redditslide/Adapters/CommentAdapter.java b/app/src/main/java/me/edgan/redditslide/Adapters/CommentAdapter.java index 2d8c67e56..3cacf42d0 100644 --- a/app/src/main/java/me/edgan/redditslide/Adapters/CommentAdapter.java +++ b/app/src/main/java/me/edgan/redditslide/Adapters/CommentAdapter.java @@ -15,7 +15,6 @@ import android.os.AsyncTask; import android.os.Build; import android.os.Handler; -import android.os.Looper; import android.util.Log; import android.util.TypedValue; import android.view.LayoutInflater; @@ -49,7 +48,6 @@ import me.edgan.redditslide.LastComments; import me.edgan.redditslide.OpenRedditLink; import me.edgan.redditslide.R; -import me.edgan.redditslide.Reddit; import me.edgan.redditslide.SettingValues; import me.edgan.redditslide.SpoilerRobotoTextView; import me.edgan.redditslide.SubmissionViews.PopulateSubmissionViewHolder; @@ -65,6 +63,7 @@ import me.edgan.redditslide.util.OnSingleClickListener; import me.edgan.redditslide.util.SubmissionParser; import me.edgan.redditslide.util.CommentStateUtil; +import me.edgan.redditslide.util.AsyncLoadMoreTask; import net.dean.jraw.ApiException; import net.dean.jraw.RedditClient; @@ -76,17 +75,11 @@ import net.dean.jraw.models.Submission; import java.io.File; -import java.io.PrintWriter; -import java.io.StringWriter; -import java.io.Writer; import java.util.ArrayList; -import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Locale; -import java.util.Map; -import java.util.TreeMap; public class CommentAdapter extends RecyclerView.Adapter { @@ -711,13 +704,16 @@ public void onSingleClick(View v) { progress.setVisibility(View.VISIBLE); holder.content.setText(R.string.comment_loading_more); currentLoading = - new AsyncLoadMore( - getRealPosition( - holder.getBindingAdapterPosition() - 2), + new AsyncLoadMoreTask( + finalNextPos, holder.getBindingAdapterPosition(), holder, - finalNextPos, - baseNode.comment.getComment().getFullName()); + baseNode.comment.getComment().getFullName(), + mContext, + CommentAdapter.this, + listView, + currentComments, + keys); currentLoading.execute(baseNode); } } @@ -742,7 +738,7 @@ public void onSingleClick(View v) { } } - AsyncLoadMore currentLoading; + public AsyncLoadMoreTask currentLoading; public String changedProfile; private void doReplySubmission(RecyclerView.ViewHolder submissionViewHolder) { @@ -1648,193 +1644,6 @@ private int getHiddenCountUpTo(int location) { return count; } - public class AsyncLoadMore extends AsyncTask { - public MoreCommentViewHolder holder; - public int holderPos; - public int position; - public int dataPos; - public String fullname; - - public AsyncLoadMore( - int position, - int holderPos, - MoreCommentViewHolder holder, - int dataPos, - String fullname) { - this.holderPos = holderPos; - this.holder = holder; - this.position = position; - this.dataPos = dataPos; - this.fullname = fullname; - } - - @Override - public void onPostExecute(Integer data) { - currentLoading = null; - if (!isCancelled() && data != null) { - shifted += data; - ((Activity) mContext) - .runOnUiThread( - new Runnable() { - @Override - public void run() { - currentComments.remove(position); - notifyItemRemoved(holderPos); - } - }); - int oldSize = currentComments.size(); - currentComments.addAll(position, finalData); - int newSize = currentComments.size(); - - for (int i2 = 0; i2 < currentComments.size(); i2++) { - keys.put(currentComments.get(i2).getName(), i2); - } - data = newSize - oldSize; - listView.setItemAnimator(new SlideRightAlphaAnimator()); - notifyItemRangeInserted(holderPos, data); - currentPos = holderPos; - toShiftTo = - ((LinearLayoutManager) listView.getLayoutManager()) - .findLastVisibleItemPosition(); - shiftFrom = - ((LinearLayoutManager) listView.getLayoutManager()) - .findFirstVisibleItemPosition(); - - } else if (data == null && currentComments.get(dataPos) instanceof MoreChildItem) { - final MoreChildItem baseNode = (MoreChildItem) currentComments.get(dataPos); - if (baseNode.children.getCount() > 0) { - holder.content.setText( - mContext.getString( - R.string.comment_load_more, baseNode.children.getCount())); - } else if (!baseNode.children.getChildrenIds().isEmpty()) { - holder.content.setText(R.string.comment_load_more_number_unknown); - } else { - holder.content.setText(R.string.thread_continue); - } - holder.loading.setVisibility(View.GONE); - } - } - - ArrayList finalData; - - @Override - protected Integer doInBackground(MoreChildItem... params) { - finalData = new ArrayList<>(); - int i = 0; - if (params.length > 0) { - try { - CommentNode node = params[0].comment; - node.loadMoreComments(Authentication.reddit); - HashMap waiting = new HashMap<>(); - - for (CommentNode n : node.walkTree()) { - if (!keys.containsKey(n.getComment().getFullName())) { - - CommentObject obj = new CommentItem(n); - ArrayList removed = new ArrayList<>(); - Map map = - new TreeMap<>(Collections.reverseOrder()); - map.putAll(waiting); - - for (Integer i2 : map.keySet()) { - if (i2 >= n.getDepth()) { - finalData.add(waiting.get(i2)); - removed.add(i2); - waiting.remove(i2); - i++; - } - } - - finalData.add(obj); - i++; - - if (n.hasMoreComments()) { - waiting.put( - n.getDepth(), new MoreChildItem(n, n.getMoreChildren())); - } - } - } - if (node.hasMoreComments()) { - finalData.add(new MoreChildItem(node, node.getMoreChildren())); - i++; - } - } catch (Exception e) { - Log.w(LogUtil.getTag(), "Cannot load more comments " + e); - Writer writer = new StringWriter(); - PrintWriter printWriter = new PrintWriter(writer); - e.printStackTrace(printWriter); - String stacktrace = writer.toString().replace(";", ","); - if (stacktrace.contains("UnknownHostException") - || stacktrace.contains("SocketTimeoutException") - || stacktrace.contains("ConnectException")) { - // is offline - final Handler mHandler = new Handler(Looper.getMainLooper()); - mHandler.post( - new Runnable() { - @Override - public void run() { - try { - new AlertDialog.Builder(mContext) - .setTitle(R.string.err_title) - .setMessage(R.string.err_connection_failed_msg) - .setNegativeButton(R.string.btn_ok, null) - .show(); - } catch (Exception ignored) { - - } - } - }); - } else if (stacktrace.contains("403 Forbidden") - || stacktrace.contains("401 Unauthorized")) { - // Un-authenticated - final Handler mHandler = new Handler(Looper.getMainLooper()); - mHandler.post( - new Runnable() { - @Override - public void run() { - try { - new AlertDialog.Builder(mContext) - .setTitle(R.string.err_title) - .setMessage(R.string.err_refused_request_msg) - .setNegativeButton(R.string.btn_no, null) - .setPositiveButton( - R.string.btn_yes, - (dialog, which) -> - Reddit.authentication - .updateToken(mContext)) - .show(); - } catch (Exception ignored) { - - } - } - }); - - } else if (stacktrace.contains("404 Not Found") - || stacktrace.contains("400 Bad Request")) { - final Handler mHandler = new Handler(Looper.getMainLooper()); - mHandler.post( - new Runnable() { - @Override - public void run() { - try { - new AlertDialog.Builder(mContext) - .setTitle(R.string.err_title) - .setMessage( - R.string.err_could_not_find_content_msg) - .setNegativeButton(R.string.btn_close, null) - .show(); - } catch (Exception ignored) { - - } - } - }); - } - return null; - } - } - return i; - } - } public class AsyncForceLoadChild extends AsyncTask { CommentNode node; diff --git a/app/src/main/java/me/edgan/redditslide/util/AsyncLoadMoreTask.java b/app/src/main/java/me/edgan/redditslide/util/AsyncLoadMoreTask.java new file mode 100644 index 000000000..cef44016f --- /dev/null +++ b/app/src/main/java/me/edgan/redditslide/util/AsyncLoadMoreTask.java @@ -0,0 +1,238 @@ +package me.edgan.redditslide.util; + +import android.app.Activity; +import android.content.Context; +import android.os.AsyncTask; +import android.os.Handler; +import android.os.Looper; +import android.util.Log; +import android.view.View; +import androidx.appcompat.app.AlertDialog; +import androidx.recyclerview.widget.RecyclerView; +import com.mikepenz.itemanimators.SlideRightAlphaAnimator; +import me.edgan.redditslide.Adapters.CommentAdapter; +import me.edgan.redditslide.Adapters.CommentObject; +import me.edgan.redditslide.Adapters.CommentItem; +import me.edgan.redditslide.Adapters.MoreChildItem; +import me.edgan.redditslide.Adapters.MoreCommentViewHolder; +import me.edgan.redditslide.Authentication; +import me.edgan.redditslide.R; +import net.dean.jraw.models.CommentNode; +import net.dean.jraw.models.MoreChildren; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.io.Writer; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +public class AsyncLoadMoreTask extends AsyncTask { + private final MoreCommentViewHolder holder; + private final int holderPos; + private final int dataPos; + public final String fullname; + + private final Context mContext; + private final CommentAdapter adapter; + private final RecyclerView listView; + private final ArrayList currentComments; + private final HashMap keys; + + private ArrayList finalData; + + public AsyncLoadMoreTask( + int dataPos, + int holderPos, + MoreCommentViewHolder holder, + String fullname, + Context context, + CommentAdapter adapter, + RecyclerView listView, + ArrayList currentComments, + HashMap keys) { + this.holderPos = holderPos; + this.holder = holder; + this.dataPos = dataPos; + this.fullname = fullname; + this.mContext = context; + this.adapter = adapter; + this.listView = listView; + this.currentComments = currentComments; + this.keys = keys; + } + + @Override + public void onPostExecute(Integer data) { + adapter.currentLoading = null; + if (!isCancelled() && data != null && data > 0) { + final int itemsToAdd = data; + + ((Activity) mContext) + .runOnUiThread( + new Runnable() { + @Override + public void run() { + // Ensure dataPos is still valid before removing + // Also check that the item is indeed a MoreChildItem, as the list might have changed + if (dataPos < currentComments.size() && currentComments.get(dataPos) instanceof MoreChildItem) { + // Remove the "Load More" item + currentComments.remove(dataPos); + adapter.notifyItemRemoved(holderPos); // Use adapter instance + + // Add new items at the same position + currentComments.addAll(dataPos, finalData); + + // Update keys for newly added items + for (int i = 0; i < finalData.size(); i++) { + keys.put(finalData.get(i).getName(), dataPos + i); + } + + // Update keys for items that came after the insertion point + for (int i = dataPos + finalData.size(); i < currentComments.size(); i++) { + keys.put(currentComments.get(i).getName(), i); + } + + + listView.setItemAnimator(new SlideRightAlphaAnimator()); + adapter.notifyItemRangeInserted(holderPos, itemsToAdd); + + } else { + Log.e(LogUtil.getTag(), "Error: dataPos " + + dataPos + " out of bounds or item not MoreChildItem during load more completion. Size=" + + currentComments.size()); + resetLoadingIndicator(); + } + } + }); + + } else if (data == null || data == 0) { // Handle null or zero case (error or no new comments) + Log.w(LogUtil.getTag(), "AsyncLoadMoreTask finished with null or zero data. Error occurred or no more comments."); + resetLoadingIndicator(); + } + } + + private void resetLoadingIndicator() { + // Ensure holder and its views are not null, and context is valid + if (holder != null && holder.loading != null && holder.content != null && mContext != null) { + ((Activity) mContext).runOnUiThread(() -> { + // Check if the item at dataPos still exists and is a MoreChildItem before resetting text + // It's possible the item was removed or changed by another operation + if (dataPos < currentComments.size() && currentComments.get(dataPos) instanceof MoreChildItem) { + final MoreChildItem baseNode = (MoreChildItem) currentComments.get(dataPos); + try { + // Use getLocalizedCount for display + String countString = baseNode.children.getLocalizedCount(); + if (baseNode.children.getCount() > 0) { + holder.content.setText(mContext.getString(R.string.comment_load_more_string_new, countString)); + } else if (!baseNode.children.getChildrenIds().isEmpty()) { + // Even if count is 0, if IDs exist, show unknown + holder.content.setText(R.string.comment_load_more_number_unknown); + } else { + // No count and no IDs means it's likely a "continue thread" link + holder.content.setText(R.string.thread_continue); + } + } catch (Exception e) { + Log.e(LogUtil.getTag(), "Error resetting loading indicator text", e); + holder.content.setText(R.string.comment_load_more_number_unknown); + } + } else { + // Item might have been removed or changed, just hide loading indicator + // Avoid setting text if the original item context is lost + Log.w(LogUtil.getTag(), "Item at dataPos " + dataPos + " no longer MoreChildItem when resetting indicator."); + } + holder.loading.setVisibility(View.GONE); + }); + } else { + Log.w(LogUtil.getTag(), "Could not reset loading indicator: holder or context was null."); + } + } + + + @Override + protected Integer doInBackground(MoreChildItem... params) { + finalData = new ArrayList<>(); + int itemsAddedCount = 0; + if (params.length > 0) { + MoreChildItem moreChildItem = params[0]; + CommentNode parentNode = moreChildItem.comment; + MoreChildren moreChildren = moreChildItem.children; + + try { + if (Authentication.reddit == null) { + Log.e(LogUtil.getTag(), "Authentication.reddit is null in AsyncLoadMoreTask"); + return null; + } + + // Fetch the next batch of comments + List newNodesListing = parentNode.loadMoreComments(Authentication.reddit); + + if (newNodesListing != null) { + for (CommentNode newNode : newNodesListing) { + // Check if the key already exists - prevents adding duplicates + if (!keys.containsKey(newNode.getComment().getFullName())) { + finalData.add(new CommentItem(newNode)); + itemsAddedCount++; + + // If this newly added node itself has more comments, add a MoreChildItem marker for it + if (newNode.hasMoreComments()) { + finalData.add(new MoreChildItem(newNode, newNode.getMoreChildren())); + itemsAddedCount++; + } + } else { + Log.w(LogUtil.getTag(), "Skipping already existing comment key: " + newNode.getComment().getFullName()); + } + } + } else { + Log.w(LogUtil.getTag(), "loadMoreComments returned null listing for node: " + parentNode.getComment().getId()); + } + + } catch (Exception e) { + Log.e(LogUtil.getTag(), "Cannot load more comments for node " + parentNode.getComment().getId(), e); + Writer writer = new StringWriter(); + PrintWriter printWriter = new PrintWriter(writer); + e.printStackTrace(printWriter); + String stacktrace = writer.toString().replace(";", ","); + + final Handler mHandler = new Handler(Looper.getMainLooper()); + mHandler.post( + () -> { + try { + // Default error messages + String message = mContext.getString(R.string.err_connection_failed_msg); + String title = mContext.getString(R.string.err_title); + Runnable positiveAction = null; + String positiveButtonText = mContext.getString(R.string.btn_ok); + + // Specific error handling based on stacktrace + if (stacktrace.contains("UnknownHostException") + || stacktrace.contains("SocketTimeoutException") + || stacktrace.contains("ConnectException")) { + message = mContext.getString(R.string.err_connection_failed_msg); + } else if (stacktrace.contains("403 Forbidden") + || stacktrace.contains("401 Unauthorized")) { + message = mContext.getString(R.string.err_refused_request_msg); + // Removed re-auth attempt for simplicity, just inform user + } else if (stacktrace.contains("404 Not Found") + || stacktrace.contains("400 Bad Request")) { + message = mContext.getString(R.string.err_could_not_find_content_msg); + } else { + message = mContext.getString(R.string.err_general) + "\n" + e.getMessage(); + } + + AlertDialog.Builder builder = new AlertDialog.Builder(mContext) + .setTitle(title) + .setMessage(message) + .setNegativeButton(R.string.btn_close, null); + + builder.setPositiveButton(positiveButtonText, null); + builder.show(); + } catch (Exception ignored) { + Log.e(LogUtil.getTag(), "Exception showing error dialog", ignored); + } + }); + return null; + } + } + return itemsAddedCount; + } +} \ No newline at end of file From e9db3396ec780f803e4f44df06501187a29aa336 Mon Sep 17 00:00:00 2001 From: PrimaryVision <155949587+PrimaryVision@users.noreply.github.com> Date: Sun, 11 May 2025 01:13:53 +0530 Subject: [PATCH 27/99] fix: Inconsistent tap behavior on comments with media --- .../redditslide/SpoilerRobotoTextView.java | 1 + .../handler/TextViewLinkHandler.java | 62 ++++++++++++++++--- 2 files changed, 55 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/me/edgan/redditslide/SpoilerRobotoTextView.java b/app/src/main/java/me/edgan/redditslide/SpoilerRobotoTextView.java index 3c266ddf5..d5d640eab 100644 --- a/app/src/main/java/me/edgan/redditslide/SpoilerRobotoTextView.java +++ b/app/src/main/java/me/edgan/redditslide/SpoilerRobotoTextView.java @@ -79,6 +79,7 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.Comparator; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; diff --git a/app/src/main/java/me/edgan/redditslide/handler/TextViewLinkHandler.java b/app/src/main/java/me/edgan/redditslide/handler/TextViewLinkHandler.java index 963490ee4..0c98a5e89 100644 --- a/app/src/main/java/me/edgan/redditslide/handler/TextViewLinkHandler.java +++ b/app/src/main/java/me/edgan/redditslide/handler/TextViewLinkHandler.java @@ -1,11 +1,13 @@ package me.edgan.redditslide.handler; +import android.util.Log; import android.os.Handler; import android.text.Layout; import android.text.Selection; import android.text.Spannable; import android.text.method.BaseMovementMethod; import android.text.style.URLSpan; +import android.text.style.ImageSpan; import android.view.MotionEvent; import android.widget.TextView; @@ -101,17 +103,61 @@ public boolean onTouchEvent(TextView widget, final Spannable buffer, MotionEvent handler.removeCallbacksAndMessages(null); if (!clickHandled) { - // regular click - if (link.length != 0) { - int i = 0; - if (sequence != null) { - i = sequence.getSpanEnd(link[0]); + URLSpan tappedUrlSpan = link[0]; + ImageSpan[] imageSpansAtTapOffset = buffer.getSpans(off, off, ImageSpan.class); + int urlSpanStart = buffer.getSpanStart(tappedUrlSpan); + int urlSpanEnd = buffer.getSpanEnd(tappedUrlSpan); + + ImageSpan[] allImageSpansInUrl = buffer.getSpans(urlSpanStart, urlSpanEnd, ImageSpan.class); + boolean hasImageInUrl = allImageSpansInUrl.length > 0; + boolean isEffectivelyImageOnlyLink = false; + + if (hasImageInUrl) { + isEffectivelyImageOnlyLink = true; + for (int i = urlSpanStart; i < urlSpanEnd; i++) { + boolean charIsImage = false; + for (ImageSpan imgSpan : allImageSpansInUrl) { + if (i >= buffer.getSpanStart(imgSpan) && i < buffer.getSpanEnd(imgSpan)) { + charIsImage = true; + break; + } + } + if (!charIsImage && !Character.isWhitespace(buffer.charAt(i))) { + isEffectivelyImageOnlyLink = false; + break; + } } - if (!link[0].getURL().isEmpty()) { - clickableText.onLinkClick(link[0].getURL(), i, subreddit, link[0]); + } + + if (isEffectivelyImageOnlyLink) { + if (imageSpansAtTapOffset.length > 0) { + ImageSpan tappedImageSpan = imageSpansAtTapOffset[0]; + android.graphics.drawable.Drawable drawable = tappedImageSpan.getDrawable(); + if (drawable != null) { + float charCellLeftEdge = layout.getPrimaryHorizontal(off); + float charCellRightEdge = layout.getSecondaryHorizontal(off); + int imageDrawableWidth = drawable.getBounds().width(); + // Drawable's visual right edge (assumes left-alignment in cell) + float visualImageDrawableRightEdge = charCellLeftEdge + imageDrawableWidth; + + if (x >= charCellRightEdge) { + Selection.removeSelection(buffer); + return false; + } else if (x > visualImageDrawableRightEdge) { + Selection.removeSelection(buffer); + return false; + } else { + clickableText.onLinkClick(tappedUrlSpan.getURL(), urlSpanEnd, subreddit, tappedUrlSpan); + } + } else { + clickableText.onLinkClick(tappedUrlSpan.getURL(), urlSpanEnd, subreddit, tappedUrlSpan); + } + } else { // Tap on whitespace within an effectively image-only link: allow expand/collapse. + Selection.removeSelection(buffer); + return false; } } else { - return false; + clickableText.onLinkClick(tappedUrlSpan.getURL(), urlSpanEnd, subreddit, tappedUrlSpan); } } break; From 1f381d186122f9d2482103c18649291fbf3a5a5d Mon Sep 17 00:00:00 2001 From: PrimaryVision <155949587+PrimaryVision@users.noreply.github.com> Date: Sun, 11 May 2025 03:46:22 +0530 Subject: [PATCH 28/99] fix: Left side of the image opens child comments --- .../redditslide/SpoilerRobotoTextView.java | 1 - .../handler/TextViewLinkHandler.java | 34 ++++++++++--------- 2 files changed, 18 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/me/edgan/redditslide/SpoilerRobotoTextView.java b/app/src/main/java/me/edgan/redditslide/SpoilerRobotoTextView.java index d5d640eab..3c266ddf5 100644 --- a/app/src/main/java/me/edgan/redditslide/SpoilerRobotoTextView.java +++ b/app/src/main/java/me/edgan/redditslide/SpoilerRobotoTextView.java @@ -79,7 +79,6 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; -import java.util.Comparator; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; diff --git a/app/src/main/java/me/edgan/redditslide/handler/TextViewLinkHandler.java b/app/src/main/java/me/edgan/redditslide/handler/TextViewLinkHandler.java index 0c98a5e89..6ad12e02e 100644 --- a/app/src/main/java/me/edgan/redditslide/handler/TextViewLinkHandler.java +++ b/app/src/main/java/me/edgan/redditslide/handler/TextViewLinkHandler.java @@ -1,6 +1,5 @@ package me.edgan.redditslide.handler; -import android.util.Log; import android.os.Handler; import android.text.Layout; import android.text.Selection; @@ -133,26 +132,29 @@ public boolean onTouchEvent(TextView widget, final Spannable buffer, MotionEvent if (imageSpansAtTapOffset.length > 0) { ImageSpan tappedImageSpan = imageSpansAtTapOffset[0]; android.graphics.drawable.Drawable drawable = tappedImageSpan.getDrawable(); - if (drawable != null) { - float charCellLeftEdge = layout.getPrimaryHorizontal(off); - float charCellRightEdge = layout.getSecondaryHorizontal(off); - int imageDrawableWidth = drawable.getBounds().width(); - // Drawable's visual right edge (assumes left-alignment in cell) - float visualImageDrawableRightEdge = charCellLeftEdge + imageDrawableWidth; - - if (x >= charCellRightEdge) { - Selection.removeSelection(buffer); - return false; - } else if (x > visualImageDrawableRightEdge) { + + if (drawable != null && drawable.getBounds().width() > 0 && drawable.getBounds().height() > 0) { + int spanStartOffset = buffer.getSpanStart(tappedImageSpan); + + float imageDrawStartX = layout.getPrimaryHorizontal(spanStartOffset); + float imageDrawEndX = imageDrawStartX + drawable.getBounds().width(); + + int imageStartLine = layout.getLineForOffset(spanStartOffset); + float imageDrawEndY = layout.getLineBottom(imageStartLine); + float imageDrawStartY = imageDrawEndY - drawable.getBounds().height(); + + if (x >= imageDrawStartX && x < imageDrawEndX && + y >= imageDrawStartY && y < imageDrawEndY) { + clickableText.onLinkClick(tappedUrlSpan.getURL(), urlSpanEnd, subreddit, tappedUrlSpan); + } else { Selection.removeSelection(buffer); return false; - } else { - clickableText.onLinkClick(tappedUrlSpan.getURL(), urlSpanEnd, subreddit, tappedUrlSpan); } } else { - clickableText.onLinkClick(tappedUrlSpan.getURL(), urlSpanEnd, subreddit, tappedUrlSpan); + Selection.removeSelection(buffer); + return false; } - } else { // Tap on whitespace within an effectively image-only link: allow expand/collapse. + } else { Selection.removeSelection(buffer); return false; } From bbd4dab9ee88a9f377a1c49d818d8696990bea08 Mon Sep 17 00:00:00 2001 From: Nathan Grennan Date: Mon, 12 May 2025 23:39:32 -0700 Subject: [PATCH 29/99] Revert "Fixed crash related to displaying submissions in a subreddit" This reverts commit 5b85bd27f556f25e8f7959caf132cb33e95e993e. --- .../edgan/redditslide/Fragments/SubmissionsView.java | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/app/src/main/java/me/edgan/redditslide/Fragments/SubmissionsView.java b/app/src/main/java/me/edgan/redditslide/Fragments/SubmissionsView.java index 45b73adc2..989780fc2 100644 --- a/app/src/main/java/me/edgan/redditslide/Fragments/SubmissionsView.java +++ b/app/src/main/java/me/edgan/redditslide/Fragments/SubmissionsView.java @@ -587,16 +587,7 @@ public void onResume() { if (adapter != null && adapterPosition > 0 && currentPosition == adapterPosition) { if (adapter.dataSet.getPosts().size() >= adapterPosition - 1 && adapter.dataSet.getPosts().get(adapterPosition - 1) == currentSubmission) { - // Use a Handler to post the click action to the main thread's message queue - // This prevents the "FragmentManager is already executing transactions" error - new Handler().post(new Runnable() { - @Override - public void run() { - if (isAdded() && adapter != null) { - adapter.performClick(adapterPosition); - } - } - }); + adapter.performClick(adapterPosition); adapterPosition = -1; } } From 572bcf9207bc21f14e02564725d61ada31d3ad3d Mon Sep 17 00:00:00 2001 From: PrimaryVision <155949587+PrimaryVision@users.noreply.github.com> Date: Fri, 16 May 2025 20:52:59 +0000 Subject: [PATCH 30/99] feat: Add playback speed control to video player --- .../redditslide/Activities/MediaView.java | 1 + .../edgan/redditslide/Views/ExoVideoView.java | 43 ++++++++++- app/src/main/res/drawable/ic_speed.xml | 24 ++++++ app/src/main/res/layout/activity_media.xml | 74 ++++++++++--------- app/src/main/res/values/strings.xml | 10 +++ 5 files changed, 116 insertions(+), 36 deletions(-) create mode 100644 app/src/main/res/drawable/ic_speed.xml diff --git a/app/src/main/java/me/edgan/redditslide/Activities/MediaView.java b/app/src/main/java/me/edgan/redditslide/Activities/MediaView.java index 22324efdd..1c62293c4 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/MediaView.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/MediaView.java @@ -626,6 +626,7 @@ public void onClick(View v) { submissionTitle); videoView.attachMuteButton((ImageView) findViewById(R.id.mute)); videoView.attachHqButton((ImageView) findViewById(R.id.hq)); + videoView.attachSpeedButton((ImageView) findViewById(R.id.speed), this); gif.execute(dat); findViewById(R.id.more) .setOnClickListener( diff --git a/app/src/main/java/me/edgan/redditslide/Views/ExoVideoView.java b/app/src/main/java/me/edgan/redditslide/Views/ExoVideoView.java index f4a2e8719..bd9875fa5 100644 --- a/app/src/main/java/me/edgan/redditslide/Views/ExoVideoView.java +++ b/app/src/main/java/me/edgan/redditslide/Views/ExoVideoView.java @@ -62,11 +62,13 @@ public class ExoVideoView extends RelativeLayout { private PlayerControlView playerUI; private boolean muteAttached = false; private boolean hqAttached = false; + private boolean speedAttached = false; + private float[] speedOptions = {0.5f, 0.75f, 1.0f, 1.25f, 1.5f, 2.0f}; + private int currentSpeedIndex = 2; // 1.0x default private AudioFocusHelper audioFocusHelper; private Handler handler = new Handler(Looper.getMainLooper()); private Runnable hideControlsRunnable; - private ScaleGestureDetector scaleGestureDetector; private float scaleFactor = 1.0f; private AspectRatioFrameLayout videoFrame; @@ -610,6 +612,45 @@ public void onTracksChanged(@NonNull Tracks tracks) { } } + /** + * Attaches a speed control button to this view. + */ + public void attachSpeedButton(final ImageView speed, final Context parentContext) { + Log.d(TAG, "attachSpeedButton() called"); + if (speed != null && player != null) { + speed.setVisibility(VISIBLE); + speed.setImageResource(R.drawable.ic_speed); + speed.setOnClickListener(v -> { + // Show a dialog to pick speed + String[] speedLabels = new String[] { + parentContext.getString(R.string.video_speed_0_5x), + parentContext.getString(R.string.video_speed_0_75x), + parentContext.getString(R.string.video_speed_1x), + parentContext.getString(R.string.video_speed_1_25x), + parentContext.getString(R.string.video_speed_1_5x), + parentContext.getString(R.string.video_speed_2x) + }; + new androidx.appcompat.app.AlertDialog.Builder(parentContext) + .setTitle(R.string.video_speed) + .setSingleChoiceItems(speedLabels, currentSpeedIndex, (dialog, which) -> { + setPlaybackSpeed(speedOptions[which]); + currentSpeedIndex = which; + dialog.dismiss(); + }) + .show(); + }); + } + } + + /** + * Sets the playback speed of the player. + */ + public void setPlaybackSpeed(float speed) { + if (player != null) { + player.setPlaybackParameters(new androidx.media3.common.PlaybackParameters(speed)); + } + } + /** Enum for video types. */ public enum VideoType { STANDARD, diff --git a/app/src/main/res/drawable/ic_speed.xml b/app/src/main/res/drawable/ic_speed.xml new file mode 100644 index 000000000..907fdb68a --- /dev/null +++ b/app/src/main/res/drawable/ic_speed.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_media.xml b/app/src/main/res/layout/activity_media.xml index 40b97b49a..722bae3ae 100644 --- a/app/src/main/res/layout/activity_media.xml +++ b/app/src/main/res/layout/activity_media.xml @@ -57,31 +57,6 @@ android:id="@+id/gifheader" android:gravity="right|bottom" android:weightSum="6"> - - - - - + + + + Hide subscribed subreddit tabs Hide subscribed subreddits as tabs, but keep navigation buttons + + + Speed + 0.5x + 0.75x + 1x (Normal) + 1.25x + 1.5x + 2x + Playback speed control \ No newline at end of file From 577f05465a170dad9037a34e66a3ba56ace18ab8 Mon Sep 17 00:00:00 2001 From: PrimaryVision <155949587+PrimaryVision@users.noreply.github.com> Date: Sat, 17 May 2025 21:46:33 +0530 Subject: [PATCH 31/99] fix: hide speed button for images and add null checks for size TextView --- .../redditslide/Activities/MediaView.java | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/me/edgan/redditslide/Activities/MediaView.java b/app/src/main/java/me/edgan/redditslide/Activities/MediaView.java index 1c62293c4..ec6a90a89 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/MediaView.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/MediaView.java @@ -444,9 +444,9 @@ public void onCreate(Bundle savedInstanceState) { } setContentView(R.layout.activity_media); - - // Keep the screen on - getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + // Hide speed button by default + ImageView speedBtn = (ImageView) findViewById(R.id.speed); + if (speedBtn != null) speedBtn.setVisibility(View.GONE); final String firstUrl = getIntent().getExtras().getString(EXTRA_DISPLAY_URL, ""); contentUrl = getIntent().getExtras().getString(EXTRA_URL); @@ -624,9 +624,12 @@ public void onClick(View v) { ((TextView) findViewById(R.id.size)), subreddit, submissionTitle); + // Show and attach speed button for GIFs + ImageView speedBtn = (ImageView) findViewById(R.id.speed); + if (speedBtn != null) speedBtn.setVisibility(View.VISIBLE); videoView.attachMuteButton((ImageView) findViewById(R.id.mute)); videoView.attachHqButton((ImageView) findViewById(R.id.hq)); - videoView.attachSpeedButton((ImageView) findViewById(R.id.speed), this); + videoView.attachSpeedButton(speedBtn, this); gif.execute(dat); findViewById(R.id.more) .setOnClickListener( @@ -1032,7 +1035,7 @@ public void onAnimationUpdate( @Override public void onLoadingStarted(String imageUri, View view) { imageShown = true; - size.setVisibility(View.VISIBLE); + if (size != null) size.setVisibility(View.VISIBLE); } @Override @@ -1046,7 +1049,7 @@ public void onLoadingFailed( public void onLoadingComplete( String imageUri, View view, Bitmap loadedImage) { imageShown = true; - size.setVisibility(View.GONE); + if (size != null) size.setVisibility(View.GONE); File f = ((Reddit) getApplicationContext()) @@ -1138,10 +1141,10 @@ public void onLoadingCancelled(String imageUri, View view) { @Override public void onProgressUpdate( String imageUri, View view, int current, int total) { - size.setText(FileUtil.readableFileSize(total)); - - ((ProgressBar) findViewById(R.id.progress)) - .setProgress(Math.round(100.0f * current / total)); + TextView size = (TextView) findViewById(R.id.size); + if (size != null) { + size.setText(String.format("%d%%", (int) (100.0 * current / total))); + } } }); } From 79dea29399ccb7c3056fe52d99548c4f92ef8ebe Mon Sep 17 00:00:00 2001 From: PrimaryVision <155949587+PrimaryVision@users.noreply.github.com> Date: Sat, 17 May 2025 22:01:50 +0530 Subject: [PATCH 32/99] Add 0.25x video speed option and change 1x label to "Normal" --- .../main/java/me/edgan/redditslide/Views/ExoVideoView.java | 5 +++-- app/src/main/res/values/strings.xml | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/me/edgan/redditslide/Views/ExoVideoView.java b/app/src/main/java/me/edgan/redditslide/Views/ExoVideoView.java index bd9875fa5..35d5fb5e1 100644 --- a/app/src/main/java/me/edgan/redditslide/Views/ExoVideoView.java +++ b/app/src/main/java/me/edgan/redditslide/Views/ExoVideoView.java @@ -63,8 +63,8 @@ public class ExoVideoView extends RelativeLayout { private boolean muteAttached = false; private boolean hqAttached = false; private boolean speedAttached = false; - private float[] speedOptions = {0.5f, 0.75f, 1.0f, 1.25f, 1.5f, 2.0f}; - private int currentSpeedIndex = 2; // 1.0x default + private float[] speedOptions = {0.25f, 0.5f, 0.75f, 1.0f, 1.25f, 1.5f, 2.0f}; + private int currentSpeedIndex = 3; // Normal (1.0x) default private AudioFocusHelper audioFocusHelper; private Handler handler = new Handler(Looper.getMainLooper()); private Runnable hideControlsRunnable; @@ -623,6 +623,7 @@ public void attachSpeedButton(final ImageView speed, final Context parentContext speed.setOnClickListener(v -> { // Show a dialog to pick speed String[] speedLabels = new String[] { + parentContext.getString(R.string.video_speed_0_25x), parentContext.getString(R.string.video_speed_0_5x), parentContext.getString(R.string.video_speed_0_75x), parentContext.getString(R.string.video_speed_1x), diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 34cd96091..9eee6ad48 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1324,9 +1324,10 @@ Speed + 0.25x 0.5x 0.75x - 1x (Normal) + Normal 1.25x 1.5x 2x From 8e6c6fab6955999555c383687d11338f8d9aa609 Mon Sep 17 00:00:00 2001 From: PrimaryVision <155949587+PrimaryVision@users.noreply.github.com> Date: Sat, 17 May 2025 22:07:41 +0530 Subject: [PATCH 33/99] Update speed control icon --- app/src/main/res/drawable/ic_speed.xml | 27 +++++--------------------- 1 file changed, 5 insertions(+), 22 deletions(-) diff --git a/app/src/main/res/drawable/ic_speed.xml b/app/src/main/res/drawable/ic_speed.xml index 907fdb68a..a42ab07f4 100644 --- a/app/src/main/res/drawable/ic_speed.xml +++ b/app/src/main/res/drawable/ic_speed.xml @@ -1,24 +1,7 @@ - - - - - - - - - + + + From 09bca94e12c6f9d120fb6dde2ebbf7fc574b2505 Mon Sep 17 00:00:00 2001 From: PrimaryVision <155949587+PrimaryVision@users.noreply.github.com> Date: Sat, 17 May 2025 22:09:49 +0530 Subject: [PATCH 34/99] Run Spotless to auto-format code --- app/src/main/java/me/edgan/redditslide/Activities/MediaView.java | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/java/me/edgan/redditslide/Activities/MediaView.java b/app/src/main/java/me/edgan/redditslide/Activities/MediaView.java index ec6a90a89..79364c0a3 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/MediaView.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/MediaView.java @@ -18,7 +18,6 @@ import android.os.Handler; import android.util.Log; import android.view.View; -import android.view.WindowManager; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ProgressBar; From 83ac2e159a549eabb7d723edaf47fdc228ee6076 Mon Sep 17 00:00:00 2001 From: PrimaryVision <155949587+PrimaryVision@users.noreply.github.com> Date: Sat, 17 May 2025 22:15:22 +0530 Subject: [PATCH 35/99] Refactor ic_speed.xml for consistent vector formatting --- app/src/main/res/drawable/ic_speed.xml | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/app/src/main/res/drawable/ic_speed.xml b/app/src/main/res/drawable/ic_speed.xml index a42ab07f4..e99aa54c7 100644 --- a/app/src/main/res/drawable/ic_speed.xml +++ b/app/src/main/res/drawable/ic_speed.xml @@ -1,7 +1,12 @@ - - - - + + + From 7b860b8d29adde64b7b7673c2ea8be169e832927 Mon Sep 17 00:00:00 2001 From: PrimaryVision <155949587+PrimaryVision@users.noreply.github.com> Date: Sat, 17 May 2025 23:12:22 +0530 Subject: [PATCH 36/99] Switch video speed picker to bottom sheet and minor tweaks --- .../edgan/redditslide/Views/ExoVideoView.java | 94 +++++++++++++++++-- 1 file changed, 85 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/me/edgan/redditslide/Views/ExoVideoView.java b/app/src/main/java/me/edgan/redditslide/Views/ExoVideoView.java index 35d5fb5e1..385d764ec 100644 --- a/app/src/main/java/me/edgan/redditslide/Views/ExoVideoView.java +++ b/app/src/main/java/me/edgan/redditslide/Views/ExoVideoView.java @@ -41,6 +41,7 @@ import androidx.media3.common.MimeTypes; import androidx.media3.common.VideoSize; + import me.edgan.redditslide.R; import me.edgan.redditslide.Reddit; import me.edgan.redditslide.SettingValues; @@ -621,7 +622,7 @@ public void attachSpeedButton(final ImageView speed, final Context parentContext speed.setVisibility(VISIBLE); speed.setImageResource(R.drawable.ic_speed); speed.setOnClickListener(v -> { - // Show a dialog to pick speed + // Show a BottomSheetDialog to pick speed String[] speedLabels = new String[] { parentContext.getString(R.string.video_speed_0_25x), parentContext.getString(R.string.video_speed_0_5x), @@ -631,14 +632,89 @@ public void attachSpeedButton(final ImageView speed, final Context parentContext parentContext.getString(R.string.video_speed_1_5x), parentContext.getString(R.string.video_speed_2x) }; - new androidx.appcompat.app.AlertDialog.Builder(parentContext) - .setTitle(R.string.video_speed) - .setSingleChoiceItems(speedLabels, currentSpeedIndex, (dialog, which) -> { - setPlaybackSpeed(speedOptions[which]); - currentSpeedIndex = which; - dialog.dismiss(); - }) - .show(); + + android.widget.ListView listView = new android.widget.ListView(parentContext); + // Custom adapter to show speed label and icon for selected + android.widget.BaseAdapter adapter = new android.widget.BaseAdapter() { + @Override + public int getCount() { return speedLabels.length; } + @Override + public Object getItem(int position) { return speedLabels[position]; } + @Override + public long getItemId(int position) { return position; } + @Override + public android.view.View getView(int position, android.view.View convertView, android.view.ViewGroup parent) { + android.content.Context ctx = parent.getContext(); + android.widget.LinearLayout layout = new android.widget.LinearLayout(ctx); + layout.setOrientation(android.widget.LinearLayout.HORIZONTAL); + layout.setPadding(0, 0, 0, 0); + layout.setGravity(android.view.Gravity.CENTER_VERTICAL); + // Label + String labelText; + if (speedLabels[position].matches("[0-9.]+x")) { + // If the label is like "2x", format to 2.00x + try { + float val = Float.parseFloat(speedLabels[position].replace("x", "")); + labelText = String.format("%.2fx", val); + } catch (Exception e) { + labelText = speedLabels[position]; + } + } else { + labelText = speedLabels[position]; + } + android.widget.TextView label = new android.widget.TextView(ctx); + label.setText(labelText); + label.setTextColor(android.graphics.Color.WHITE); + // Use default text appearance for list items + label.setTextAppearance(android.R.style.TextAppearance_Material_Body1); + label.setPadding((int)(ctx.getResources().getDisplayMetrics().density*4), (int)(ctx.getResources().getDisplayMetrics().density*8), (int)(ctx.getResources().getDisplayMetrics().density*4), (int)(ctx.getResources().getDisplayMetrics().density*8)); + android.widget.LinearLayout.LayoutParams labelParams = new android.widget.LinearLayout.LayoutParams(0, android.widget.LinearLayout.LayoutParams.WRAP_CONTENT, 1f); + label.setLayoutParams(labelParams); + layout.addView(label); + // Icon for selected + if (position == currentSpeedIndex) { + ImageView icon = new ImageView(ctx); + icon.setImageResource(R.drawable.ic_speed); + icon.setColorFilter(android.graphics.Color.WHITE, android.graphics.PorterDuff.Mode.SRC_IN); + int iconSize = (int)(ctx.getResources().getDisplayMetrics().density*24); + android.widget.LinearLayout.LayoutParams iconParams = new android.widget.LinearLayout.LayoutParams(iconSize, iconSize); + iconParams.setMarginStart((int)(ctx.getResources().getDisplayMetrics().density*8)); + icon.setLayoutParams(iconParams); + layout.addView(icon); + } + return layout; + } + }; + listView.setAdapter(adapter); + listView.setChoiceMode(android.widget.ListView.CHOICE_MODE_SINGLE); + listView.setItemChecked(currentSpeedIndex, true); + listView.setBackgroundColor(android.graphics.Color.BLACK); + listView.setDivider(null); // Remove the separator + listView.setDividerHeight(0); // Ensure no divider is shown + int horizontalPadding = (int) (parentContext.getResources().getDisplayMetrics().density * 24); // 24dp + int topPadding = (int) (parentContext.getResources().getDisplayMetrics().density * 12); // 12dp + listView.setPadding(horizontalPadding, topPadding, horizontalPadding, listView.getPaddingBottom()); + listView.setClipToPadding(false); + + com.google.android.material.bottomsheet.BottomSheetDialog bottomSheetDialog = new com.google.android.material.bottomsheet.BottomSheetDialog(parentContext); + bottomSheetDialog.setContentView(listView); + bottomSheetDialog.setTitle(parentContext.getString(R.string.video_speed)); + + // Set the background of the bottom sheet itself to black (no rounded corners) + bottomSheetDialog.setOnShowListener(dialog -> { + android.view.View bottomSheet = bottomSheetDialog.findViewById(com.google.android.material.R.id.design_bottom_sheet); + if (bottomSheet != null) { + bottomSheet.setBackgroundColor(android.graphics.Color.BLACK); + } + }); + + listView.setOnItemClickListener((parent, view, position, id) -> { + setPlaybackSpeed(speedOptions[position]); + currentSpeedIndex = position; + bottomSheetDialog.dismiss(); + }); + + bottomSheetDialog.show(); }); } } From 4376f50045d2e053a60f6271dc35fb762ea57490 Mon Sep 17 00:00:00 2001 From: PrimaryVision <155949587+PrimaryVision@users.noreply.github.com> Date: Sun, 18 May 2025 01:52:36 +0530 Subject: [PATCH 37/99] Add speed control icon to Pager layouts --- .../redditslide/Activities/AlbumPager.java | 104 +++++++++++------- .../redditslide/Activities/RedditGallery.java | 10 ++ .../Activities/RedditGalleryPager.java | 82 ++++++++------ .../res/layout/submission_gifcard_album.xml | 15 ++- 4 files changed, 140 insertions(+), 71 deletions(-) diff --git a/app/src/main/java/me/edgan/redditslide/Activities/AlbumPager.java b/app/src/main/java/me/edgan/redditslide/Activities/AlbumPager.java index 6c70065f7..4310804f7 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/AlbumPager.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/AlbumPager.java @@ -498,6 +498,16 @@ public View onCreateView( v.attachHqButton(hqButton); } + ImageView speedButton = rootView.findViewById(R.id.speed); + if (speedButton != null) { + if (((AlbumPager) getActivity()).images.get(i).isAnimated()) { + speedButton.setVisibility(View.VISIBLE); + v.attachSpeedButton(speedButton, getActivity()); + } else { + speedButton.setVisibility(View.GONE); + } + } + final String url = ((AlbumPager) getActivity()).images.get(i).getImageUrl(); // Important: Always start with autostart=false @@ -749,30 +759,58 @@ public View onCreateView( loadImage(rootView, this, url, ((AlbumPager) getActivity()).images.size() == 1); } - { - rootView.findViewById(R.id.more) - .setOnClickListener( - new View.OnClickListener() { - @Override - public void onClick(View v) { - ((AlbumPager) getActivity()) - .showBottomSheetImage(url, false, i); - } - }); - { - rootView.findViewById(R.id.save) - .setOnClickListener( - new View.OnClickListener() { - - @Override - public void onClick(View v2) { - ((AlbumPager) getActivity()) - .doImageSave(false, url, i); - } - }); - if (!SettingValues.imageDownloadButton) { - rootView.findViewById(R.id.save).setVisibility(View.INVISIBLE); - } + View more = rootView.findViewById(R.id.more); + if (more != null) { + more.setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View v) { + ((AlbumPager) getActivity()) + .showBottomSheetImage(url, false, i); + } + }); + } + View save = rootView.findViewById(R.id.save); + if (save != null) { + save.setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View v2) { + ((AlbumPager) getActivity()) + .doImageSave(false, url, i); + } + }); + if (!SettingValues.imageDownloadButton) { + save.setVisibility(View.INVISIBLE); + } + } + View panel = rootView.findViewById(R.id.panel); + if (panel != null) { + panel.setVisibility(View.GONE); + } + View margin = rootView.findViewById(R.id.margin); + if (margin != null) { + margin.setPadding(0, 0, 0, 0); + } + View hq = rootView.findViewById(R.id.hq); + if (hq != null) { + hq.setVisibility(View.GONE); + } + View comments = rootView.findViewById(R.id.comments); + if (getActivity().getIntent().hasExtra(MediaView.SUBMISSION_URL)) { + if (comments != null) { + comments.setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View v) { + getActivity().finish(); + SubmissionsView.datachanged(adapterPosition); + } + }); + } + } else { + if (comments != null) { + comments.setVisibility(View.GONE); } } { @@ -842,6 +880,10 @@ public void onClick(View v) { } }); } + View mute = rootView.findViewById(R.id.mute); + if (mute != null) { + mute.setVisibility(View.GONE); + } if (lq) { rootView.findViewById(R.id.hq) .setOnClickListener( @@ -860,20 +902,6 @@ public void onClick(View v) { } else { rootView.findViewById(R.id.hq).setVisibility(View.GONE); } - - if (getActivity().getIntent().hasExtra(MediaView.SUBMISSION_URL)) { - rootView.findViewById(R.id.comments) - .setOnClickListener( - new View.OnClickListener() { - @Override - public void onClick(View v) { - getActivity().finish(); - SubmissionsView.datachanged(adapterPosition); - } - }); - } else { - rootView.findViewById(R.id.comments).setVisibility(View.GONE); - } } return rootView; } diff --git a/app/src/main/java/me/edgan/redditslide/Activities/RedditGallery.java b/app/src/main/java/me/edgan/redditslide/Activities/RedditGallery.java index dccdfcc0c..e2e2a2a1b 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/RedditGallery.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/RedditGallery.java @@ -482,6 +482,16 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa } rootView.findViewById(R.id.mute).setVisibility(View.GONE); rootView.findViewById(R.id.hq).setVisibility(View.GONE); + + ImageView speedButton = rootView.findViewById(R.id.speed); + if (speedButton != null) { + if (current != null && current.isAnimated()) { + speedButton.setVisibility(View.VISIBLE); + exoVideoView.attachSpeedButton(speedButton, getActivity()); + } else { + speedButton.setVisibility(View.GONE); + } + } } } diff --git a/app/src/main/java/me/edgan/redditslide/Activities/RedditGalleryPager.java b/app/src/main/java/me/edgan/redditslide/Activities/RedditGalleryPager.java index 3e8a088d3..02bea6abf 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/RedditGalleryPager.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/RedditGalleryPager.java @@ -468,44 +468,62 @@ public View onCreateView( ((RedditGalleryPager) getActivity()).images.size() == 1); } - rootView.findViewById(R.id.more) - .setOnClickListener( + View more = rootView.findViewById(R.id.more); + if (more != null) { + more.setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View v) { + ((RedditGalleryPager) getActivity()) + .showBottomSheetImage(url, false, i); + } + }); + } + View save = rootView.findViewById(R.id.save); + if (save != null) { + save.setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View v2) { + ((RedditGalleryPager) getActivity()).doImageSave(false, url, i); + } + }); + if (!SettingValues.imageDownloadButton) { + save.setVisibility(View.INVISIBLE); + } + } + View panel = rootView.findViewById(R.id.panel); + if (panel != null) { + panel.setVisibility(View.GONE); + } + View margin = rootView.findViewById(R.id.margin); + if (margin != null) { + margin.setPadding(0, 0, 0, 0); + } + View hq = rootView.findViewById(R.id.hq); + if (hq != null) { + hq.setVisibility(View.GONE); + } + View mute = rootView.findViewById(R.id.mute); + if (mute != null) { + mute.setVisibility(View.GONE); + } + View comments = rootView.findViewById(R.id.comments); + if (getActivity().getIntent().hasExtra(MediaView.SUBMISSION_URL)) { + if (comments != null) { + comments.setOnClickListener( new View.OnClickListener() { @Override public void onClick(View v) { - ((RedditGalleryPager) getActivity()) - .showBottomSheetImage(url, false, i); - } - }); - rootView.findViewById(R.id.save) - .setOnClickListener( - new View.OnClickListener() { - @Override - public void onClick(View v2) { - ((RedditGalleryPager) getActivity()).doImageSave(false, url, i); + getActivity().finish(); + SubmissionsView.datachanged(adapterPosition); } }); - if (!SettingValues.imageDownloadButton) { - rootView.findViewById(R.id.save).setVisibility(View.INVISIBLE); - } - - rootView.findViewById(R.id.panel).setVisibility(View.GONE); - (rootView.findViewById(R.id.margin)).setPadding(0, 0, 0, 0); - - rootView.findViewById(R.id.hq).setVisibility(View.GONE); - - if (getActivity().getIntent().hasExtra(MediaView.SUBMISSION_URL)) { - rootView.findViewById(R.id.comments) - .setOnClickListener( - new View.OnClickListener() { - @Override - public void onClick(View v) { - getActivity().finish(); - SubmissionsView.datachanged(adapterPosition); - } - }); + } } else { - rootView.findViewById(R.id.comments).setVisibility(View.GONE); + if (comments != null) { + comments.setVisibility(View.GONE); + } } return rootView; } diff --git a/app/src/main/res/layout/submission_gifcard_album.xml b/app/src/main/res/layout/submission_gifcard_album.xml index f3348cdf6..2ce42b215 100644 --- a/app/src/main/res/layout/submission_gifcard_album.xml +++ b/app/src/main/res/layout/submission_gifcard_album.xml @@ -67,10 +67,23 @@ android:layout_height="56dp" android:theme="@style/Ripple.List" android:padding="15dp" - android:layout_toLeftOf="@+id/save" + android:layout_toLeftOf="@+id/speed" app:srcCompat="@drawable/ic_volume_off" android:tint="#e1e1e1" /> + + Date: Sun, 18 May 2025 15:41:08 +0530 Subject: [PATCH 38/99] Add comment icon for videos in pager layout --- .../redditslide/Activities/AlbumPager.java | 17 ++++++++++++++ .../redditslide/Activities/RedditGallery.java | 23 +++++++++++++++++++ .../res/layout/submission_gifcard_album.xml | 12 +++++++++- 3 files changed, 51 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/me/edgan/redditslide/Activities/AlbumPager.java b/app/src/main/java/me/edgan/redditslide/Activities/AlbumPager.java index 4310804f7..a4ba9ad51 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/AlbumPager.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/AlbumPager.java @@ -584,6 +584,23 @@ public void onClick(View v) { if (!SettingValues.imageDownloadButton) { rootView.findViewById(R.id.save).setVisibility(View.INVISIBLE); } + + // Add comment button logic + View comments = rootView.findViewById(R.id.comments); + if (comments != null) { + if (getActivity().getIntent().hasExtra(MediaView.SUBMISSION_URL)) { + comments.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + getActivity().finish(); + SubmissionsView.datachanged(adapterPosition); + } + }); + } else { + comments.setVisibility(View.GONE); + } + } + return rootView; } diff --git a/app/src/main/java/me/edgan/redditslide/Activities/RedditGallery.java b/app/src/main/java/me/edgan/redditslide/Activities/RedditGallery.java index e2e2a2a1b..bc7fb33e5 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/RedditGallery.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/RedditGallery.java @@ -395,6 +395,16 @@ public static class Gif extends Fragment { private View gifView; private ProgressBar loader; + // Helper method to get adapter position from activity + private int getAdapterPositionFromActivity(android.app.Activity activity) { + if (activity instanceof RedditGallery) { + return ((RedditGallery) activity).adapterPosition; + } else if (activity instanceof RedditGalleryPager) { + return activity.getIntent().getIntExtra(MediaView.ADAPTER_POSITION, -1); + } + return -1; + } + // Override this in subclasses to provide appropriate parent protected GalleryParent getGalleryParent() { return (RedditGallery) getActivity(); @@ -492,6 +502,19 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa speedButton.setVisibility(View.GONE); } } + + // Add comment button logic + View comments = rootView.findViewById(R.id.comments); + if (comments != null) { + if (getActivity().getIntent().hasExtra(MediaView.SUBMISSION_URL)) { + comments.setOnClickListener(v -> { + getActivity().finish(); + SubmissionsView.datachanged(getAdapterPositionFromActivity(getActivity())); + }); + } else { + comments.setVisibility(View.GONE); + } + } } } diff --git a/app/src/main/res/layout/submission_gifcard_album.xml b/app/src/main/res/layout/submission_gifcard_album.xml index 2ce42b215..c2d8fe8b9 100644 --- a/app/src/main/res/layout/submission_gifcard_album.xml +++ b/app/src/main/res/layout/submission_gifcard_album.xml @@ -41,13 +41,23 @@ android:layout_below="@+id/imagearea" android:layout_marginTop="-52dp"> + + From ad9c2c06c7bee6aa7e5a3ee7cfb856a0cb3ba480 Mon Sep 17 00:00:00 2001 From: PrimaryVision <155949587+PrimaryVision@users.noreply.github.com> Date: Sun, 18 May 2025 23:56:04 +0530 Subject: [PATCH 39/99] Ensured icon bar for pager videos is visually consistent with images --- .../res/layout/submission_gifcard_album.xml | 231 +++++++++--------- 1 file changed, 121 insertions(+), 110 deletions(-) diff --git a/app/src/main/res/layout/submission_gifcard_album.xml b/app/src/main/res/layout/submission_gifcard_album.xml index c2d8fe8b9..4b4916713 100644 --- a/app/src/main/res/layout/submission_gifcard_album.xml +++ b/app/src/main/res/layout/submission_gifcard_album.xml @@ -1,134 +1,145 @@ - - - + android:gravity="bottom" + sothree:umanoOverlay="true" + sothree:umanoPanelHeight="48dp" + sothree:umanoShadowHeight="4dp"> + + android:orientation="vertical"> - - - - + + + + + + + - - - - - - - + + + + - - - - - + + + + + + + + + + - - + android:id="@+id/playbutton" + android:layout_width="72dp" + android:layout_height="72dp" + android:layout_centerInParent="true" + android:alpha="0.8" + android:background="@drawable/circle_background" + android:padding="16dp" + android:src="@drawable/ic_play" + android:visibility="visible" /> - - - - + + + + + From f531a63c790081252062d5a96ab8a871cc560a13 Mon Sep 17 00:00:00 2001 From: PrimaryVision <155949587+PrimaryVision@users.noreply.github.com> Date: Mon, 19 May 2025 01:09:47 +0530 Subject: [PATCH 40/99] Hide comment icon for GIF/videos in vertical mode --- .../main/java/me/edgan/redditslide/Adapters/AlbumView.java | 4 ++++ .../java/me/edgan/redditslide/Adapters/RedditGalleryView.java | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/app/src/main/java/me/edgan/redditslide/Adapters/AlbumView.java b/app/src/main/java/me/edgan/redditslide/Adapters/AlbumView.java index deb1d2f94..594865816 100644 --- a/app/src/main/java/me/edgan/redditslide/Adapters/AlbumView.java +++ b/app/src/main/java/me/edgan/redditslide/Adapters/AlbumView.java @@ -242,6 +242,10 @@ public void onClick(View view) { holder.saveButton.setVisibility(View.GONE); holder.moreButton.setVisibility(View.GONE); + View commentsButton = holder.rootView.findViewById(R.id.comments); + if (commentsButton != null) { + commentsButton.setVisibility(View.GONE); + } holder.muteButton.setVisibility(View.GONE); holder.hqButton.setVisibility(View.GONE); diff --git a/app/src/main/java/me/edgan/redditslide/Adapters/RedditGalleryView.java b/app/src/main/java/me/edgan/redditslide/Adapters/RedditGalleryView.java index 5f5af7af5..6f5f35589 100644 --- a/app/src/main/java/me/edgan/redditslide/Adapters/RedditGalleryView.java +++ b/app/src/main/java/me/edgan/redditslide/Adapters/RedditGalleryView.java @@ -288,6 +288,10 @@ public void onClick(View v) { holder.saveButton.setVisibility(View.GONE); holder.moreButton.setVisibility(View.GONE); + View commentsButton = holder.rootView.findViewById(R.id.comments); + if (commentsButton != null) { + commentsButton.setVisibility(View.GONE); + } // Hide the "save" button if user preference is off if (!SettingValues.imageDownloadButton) { From 05f848c3ddac667951feaae5598b772a80fc4b1c Mon Sep 17 00:00:00 2001 From: PrimaryVision <155949587+PrimaryVision@users.noreply.github.com> Date: Mon, 19 May 2025 01:38:26 +0530 Subject: [PATCH 41/99] Video in pager layout now fills space like images --- app/src/main/res/layout/submission_gifcard_album.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/layout/submission_gifcard_album.xml b/app/src/main/res/layout/submission_gifcard_album.xml index 4b4916713..87af62c9f 100644 --- a/app/src/main/res/layout/submission_gifcard_album.xml +++ b/app/src/main/res/layout/submission_gifcard_album.xml @@ -8,7 +8,7 @@ android:gravity="bottom" sothree:umanoOverlay="true" sothree:umanoPanelHeight="48dp" - sothree:umanoShadowHeight="4dp"> + sothree:umanoShadowHeight="0dp"> Date: Sun, 18 May 2025 21:53:34 +0000 Subject: [PATCH 42/99] Fix: Add adapterPosition bounds checks in onResume --- .../edgan/redditslide/Fragments/SubmissionsView.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/me/edgan/redditslide/Fragments/SubmissionsView.java b/app/src/main/java/me/edgan/redditslide/Fragments/SubmissionsView.java index 989780fc2..1988cf2fc 100644 --- a/app/src/main/java/me/edgan/redditslide/Fragments/SubmissionsView.java +++ b/app/src/main/java/me/edgan/redditslide/Fragments/SubmissionsView.java @@ -585,10 +585,12 @@ public void onCreate(Bundle savedInstanceState) { public void onResume() { super.onResume(); if (adapter != null && adapterPosition > 0 && currentPosition == adapterPosition) { - if (adapter.dataSet.getPosts().size() >= adapterPosition - 1 - && adapter.dataSet.getPosts().get(adapterPosition - 1) == currentSubmission) { - adapter.performClick(adapterPosition); - adapterPosition = -1; + List postsList = adapter.dataSet.getPosts(); + if (postsList != null && !postsList.isEmpty() && (adapterPosition - 1) >= 0 && (adapterPosition - 1) < postsList.size()) { + if (postsList.get(adapterPosition - 1) == currentSubmission) { + adapter.performClick(adapterPosition); + adapterPosition = -1; + } } } } From 6fb9949917c0ded5adb353549be00664ae212c2e Mon Sep 17 00:00:00 2001 From: PrimaryVision <155949587+PrimaryVision@users.noreply.github.com> Date: Mon, 19 May 2025 15:09:49 +0000 Subject: [PATCH 43/99] Refactor: Simplify playback control logic and remove unnecessary delays in AlbumPager --- .../redditslide/Activities/AlbumPager.java | 94 +++++-------------- 1 file changed, 24 insertions(+), 70 deletions(-) diff --git a/app/src/main/java/me/edgan/redditslide/Activities/AlbumPager.java b/app/src/main/java/me/edgan/redditslide/Activities/AlbumPager.java index 6c70065f7..b66e5d7d5 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/AlbumPager.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/AlbumPager.java @@ -262,24 +262,6 @@ public void run() { // Reset the currently playing position Gif.currentlyPlayingPosition = -1; - // Initialize the correct position after a delay - new Handler().postDelayed(() -> { - if (!isFinishing()) { - int position = p.getCurrentItem(); - int adjustedPosition = position; - if (SettingValues.oldSwipeMode && position > 0) { - adjustedPosition = position - 1; - } - - if (images != null && !images.isEmpty() && adjustedPosition >= 0 && adjustedPosition < images.size()) { - if (images.get(adjustedPosition).isAnimated()) { - LogUtil.v("Setting initial playing position to " + adjustedPosition); - Gif.currentlyPlayingPosition = adjustedPosition; - } - } - } - }, 100); - findViewById(R.id.grid) .setOnClickListener( new View.OnClickListener() { @@ -434,34 +416,43 @@ public static class Gif extends Fragment { ProgressBar loader; // Static tracking of which fragment is currently playing - private static int currentlyPlayingPosition = -1; + // Use package-private for access from AlbumPager + static int currentlyPlayingPosition = -1; @Override public void setUserVisibleHint(boolean isVisibleToUser) { super.setUserVisibleHint(isVisibleToUser); - - // If fragment is becoming visible - if (isVisibleToUser) { - // Record this position as the currently playing fragment + // Only play if this fragment is visible and is the current playing position + if (isVisibleToUser && getCurrentPagerPosition() == i) { currentlyPlayingPosition = i; - LogUtil.v("Gif fragment " + i + " becoming visible, setting as current"); - - // Only start playback if view is created if (gif != null && gif instanceof ExoVideoView) { - LogUtil.v("Playing gif at position " + i); ((ExoVideoView) gif).play(); gif.setVisibility(View.VISIBLE); } } else { - // If this fragment is becoming invisible and it's the one that was playing - if (currentlyPlayingPosition == i && gif != null && gif instanceof ExoVideoView) { - LogUtil.v("Pausing gif at position " + i); + if (gif != null && gif instanceof ExoVideoView) { ((ExoVideoView) gif).pause(); gif.setVisibility(View.GONE); } } } + private int getCurrentPagerPosition() { + Activity activity = getActivity(); + if (activity instanceof AlbumPager) { + ViewPager pager = activity.findViewById(R.id.images_horizontal); + if (pager != null) { + int pos = pager.getCurrentItem(); + if (SettingValues.oldSwipeMode && pos > 0) { + return pos - 1; + } else { + return pos; + } + } + } + return -1; + } + @Override public View onCreateView( LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { @@ -514,41 +505,6 @@ public View onCreateView( getActivity().getIntent().getStringExtra(EXTRA_SUBMISSION_TITLE)) .execute(url); - // Add a delayed check to control playback once the UI is fully set up - // This helps overcome timing issues with fragment visibility - new Handler().postDelayed(() -> { - if (getActivity() != null && !getActivity().isFinishing() && gif != null) { - // Initialize the current playing position if it's not set yet - if (currentlyPlayingPosition == -1 && getActivity() instanceof AlbumPager) { - ViewPager pager = getActivity().findViewById(R.id.images_horizontal); - if (pager != null) { - int currentItem = pager.getCurrentItem(); - int adjustedPosition = currentItem; - if (SettingValues.oldSwipeMode && currentItem > 0) { - adjustedPosition = currentItem - 1; - } - - // This is needed to handle the initial case - if (i == adjustedPosition) { - LogUtil.v("Initial load: setting position " + i + " as current"); - currentlyPlayingPosition = i; - } - } - } - - // Check if this is the current playing position - if (getUserVisibleHint() && (currentlyPlayingPosition == i || currentlyPlayingPosition == -1)) { - LogUtil.v("Playing gif at position " + i + " after delay"); - ((ExoVideoView) gif).play(); - // Ensure we update the playing position - currentlyPlayingPosition = i; - } else { - LogUtil.v("Not playing gif at position " + i + " after delay"); - ((ExoVideoView) gif).pause(); - } - } - }, 500); // Increased delay to give more time for ViewPager to settle - rootView.findViewById(R.id.more) .setOnClickListener( new View.OnClickListener() { @@ -587,8 +543,8 @@ public void onCreate(Bundle savedInstanceState) { @Override public void onResume() { super.onResume(); - // When fragment resumes, check if it should be playing - if (getUserVisibleHint() && currentlyPlayingPosition == i && + // Only play if this fragment is visible and is the current playing position + if (getUserVisibleHint() && getCurrentPagerPosition() == i && gif != null && gif instanceof ExoVideoView) { ((ExoVideoView) gif).play(); } @@ -597,7 +553,6 @@ public void onResume() { @Override public void onPause() { super.onPause(); - // Always pause when the fragment is paused if (gif != null && gif instanceof ExoVideoView) { ((ExoVideoView) gif).pause(); } @@ -606,7 +561,6 @@ public void onPause() { @Override public void onDestroyView() { super.onDestroyView(); - // Clean up if (gif != null && gif instanceof ExoVideoView) { ((ExoVideoView) gif).pause(); } @@ -880,7 +834,7 @@ public void onClick(View v) { @Override public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); + super.onCreate(Bundle); Bundle bundle = this.getArguments(); i = bundle.getInt("page", 0); } From 284ddcd07c942c8796c3c8f5a0f29c9c94e7a861 Mon Sep 17 00:00:00 2001 From: PrimaryVision <155949587+PrimaryVision@users.noreply.github.com> Date: Mon, 19 May 2025 15:16:49 +0000 Subject: [PATCH 44/99] Improve playback management for GIFs in AlbumPager --- .../redditslide/Activities/AlbumPager.java | 47 ++++++++++--------- 1 file changed, 25 insertions(+), 22 deletions(-) diff --git a/app/src/main/java/me/edgan/redditslide/Activities/AlbumPager.java b/app/src/main/java/me/edgan/redditslide/Activities/AlbumPager.java index b66e5d7d5..e7a3b2889 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/AlbumPager.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/AlbumPager.java @@ -331,16 +331,16 @@ public void onPageScrolled( @Override public void onPageSelected(int position) { - // When a page is selected, explicitly tell all Gif fragments to check their visibility if (adapter != null && adapter.getCount() > 0) { int adjustedPosition = position; if (SettingValues.oldSwipeMode && position > 0) { adjustedPosition = position - 1; } - - // Update the currently playing position in the Gif class - if (images.get(adjustedPosition).isAnimated()) { - Gif.currentlyPlayingPosition = adjustedPosition; + for (int i = 0; i < adapter.getCount(); i++) { + Fragment frag = ((AlbumViewPagerAdapter)adapter).getFragment(i); + if (frag instanceof Gif) { + ((Gif)frag).setPlaybackActive(i == position || i == adjustedPosition); + } } } } @@ -364,6 +364,7 @@ public boolean onCreateOptionsMenu(Menu menu) { } private class AlbumViewPagerAdapter extends FragmentStatePagerAdapter { + private final List fragmentRefs = new ArrayList<>(); AlbumViewPagerAdapter(FragmentManager m) { super(m, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT); } @@ -373,29 +374,31 @@ private class AlbumViewPagerAdapter extends FragmentStatePagerAdapter { public Fragment getItem(int i) { if (SettingValues.oldSwipeMode) { if (i == 0) { - return new BlankFragment(); + Fragment blank = new BlankFragment(); + fragmentRefs.add(blank); + return blank; } - i--; } - Image current = images.get(i); - Fragment f; - if (current.isAnimated()) { f = new Gif(); } else { f = new ImageFullNoSubmission(); } - Bundle args = new Bundle(); args.putInt("page", i); f.setArguments(args); - + fragmentRefs.add(f); return f; } + public Fragment getFragment(int pos) { + if (pos >= 0 && pos < fragmentRefs.size()) return fragmentRefs.get(pos); + return null; + } + @Override public int getCount() { if (images == null) { @@ -419,24 +422,24 @@ public static class Gif extends Fragment { // Use package-private for access from AlbumPager static int currentlyPlayingPosition = -1; - @Override - public void setUserVisibleHint(boolean isVisibleToUser) { - super.setUserVisibleHint(isVisibleToUser); - // Only play if this fragment is visible and is the current playing position - if (isVisibleToUser && getCurrentPagerPosition() == i) { - currentlyPlayingPosition = i; - if (gif != null && gif instanceof ExoVideoView) { + public void setPlaybackActive(boolean active) { + if (gif != null && gif instanceof ExoVideoView) { + if (active) { ((ExoVideoView) gif).play(); gif.setVisibility(View.VISIBLE); - } - } else { - if (gif != null && gif instanceof ExoVideoView) { + } else { ((ExoVideoView) gif).pause(); gif.setVisibility(View.GONE); } } } + @Override + public void setUserVisibleHint(boolean isVisibleToUser) { + super.setUserVisibleHint(isVisibleToUser); + // No-op: playback is now controlled by onPageSelected + } + private int getCurrentPagerPosition() { Activity activity = getActivity(); if (activity instanceof AlbumPager) { From c823ceb1b1c090316cc40b937902934e094ee9d2 Mon Sep 17 00:00:00 2001 From: PrimaryVision <155949587+PrimaryVision@users.noreply.github.com> Date: Mon, 19 May 2025 20:58:44 +0530 Subject: [PATCH 45/99] Fix: Correct argument passing in onCreate method of AlbumPager --- .../main/java/me/edgan/redditslide/Activities/AlbumPager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/me/edgan/redditslide/Activities/AlbumPager.java b/app/src/main/java/me/edgan/redditslide/Activities/AlbumPager.java index e7a3b2889..c24782b2c 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/AlbumPager.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/AlbumPager.java @@ -837,7 +837,7 @@ public void onClick(View v) { @Override public void onCreate(Bundle savedInstanceState) { - super.onCreate(Bundle); + super.onCreate(savedInstanceState); Bundle bundle = this.getArguments(); i = bundle.getInt("page", 0); } From c88c50257e240ad902f1b4b8b93045ad6830d16d Mon Sep 17 00:00:00 2001 From: PrimaryVision <155949587+PrimaryVision@users.noreply.github.com> Date: Mon, 19 May 2025 21:03:17 +0530 Subject: [PATCH 46/99] Fix: Change GIF handling to stop and release resources in AlbumPager --- .../main/java/me/edgan/redditslide/Activities/AlbumPager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/me/edgan/redditslide/Activities/AlbumPager.java b/app/src/main/java/me/edgan/redditslide/Activities/AlbumPager.java index c24782b2c..634eb4b07 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/AlbumPager.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/AlbumPager.java @@ -428,7 +428,7 @@ public void setPlaybackActive(boolean active) { ((ExoVideoView) gif).play(); gif.setVisibility(View.VISIBLE); } else { - ((ExoVideoView) gif).pause(); + ((ExoVideoView) gif).stop(); // Stop and release resources gif.setVisibility(View.GONE); } } From c6872905575f283427199efb9f97bc7b3dfd382f Mon Sep 17 00:00:00 2001 From: PrimaryVision <155949587+PrimaryVision@users.noreply.github.com> Date: Mon, 19 May 2025 21:05:43 +0530 Subject: [PATCH 47/99] Fix: Update GIF playback activation logic in AlbumPager --- .../main/java/me/edgan/redditslide/Activities/AlbumPager.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/me/edgan/redditslide/Activities/AlbumPager.java b/app/src/main/java/me/edgan/redditslide/Activities/AlbumPager.java index 634eb4b07..230a2db71 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/AlbumPager.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/AlbumPager.java @@ -339,7 +339,7 @@ public void onPageSelected(int position) { for (int i = 0; i < adapter.getCount(); i++) { Fragment frag = ((AlbumViewPagerAdapter)adapter).getFragment(i); if (frag instanceof Gif) { - ((Gif)frag).setPlaybackActive(i == position || i == adjustedPosition); + ((Gif)frag).setPlaybackActive(i == adjustedPosition); } } } From 8c7e866ef9f17f6eba13148cae2609a70952ac41 Mon Sep 17 00:00:00 2001 From: PrimaryVision <155949587+PrimaryVision@users.noreply.github.com> Date: Mon, 19 May 2025 21:09:09 +0530 Subject: [PATCH 48/99] Fix: Update fragment retrieval logic for GIF playback in AlbumPager --- .../main/java/me/edgan/redditslide/Activities/AlbumPager.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/me/edgan/redditslide/Activities/AlbumPager.java b/app/src/main/java/me/edgan/redditslide/Activities/AlbumPager.java index 230a2db71..a755ea139 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/AlbumPager.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/AlbumPager.java @@ -336,8 +336,10 @@ public void onPageSelected(int position) { if (SettingValues.oldSwipeMode && position > 0) { adjustedPosition = position - 1; } + FragmentManager fm = getSupportFragmentManager(); for (int i = 0; i < adapter.getCount(); i++) { - Fragment frag = ((AlbumViewPagerAdapter)adapter).getFragment(i); + String tag = "android:switcher:" + R.id.images_horizontal + ":" + i; + Fragment frag = fm.findFragmentByTag(tag); if (frag instanceof Gif) { ((Gif)frag).setPlaybackActive(i == adjustedPosition); } From 4b1d6b3b9f25672dc9b6d5111d4128fb270c6edb Mon Sep 17 00:00:00 2001 From: PrimaryVision <155949587+PrimaryVision@users.noreply.github.com> Date: Mon, 19 May 2025 21:17:53 +0530 Subject: [PATCH 49/99] Refactor: Remove unused fragment references in AlbumPager --- .../me/edgan/redditslide/Activities/AlbumPager.java | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/app/src/main/java/me/edgan/redditslide/Activities/AlbumPager.java b/app/src/main/java/me/edgan/redditslide/Activities/AlbumPager.java index a755ea139..20e30afdf 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/AlbumPager.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/AlbumPager.java @@ -14,7 +14,6 @@ import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; -import android.os.Handler; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; @@ -366,7 +365,6 @@ public boolean onCreateOptionsMenu(Menu menu) { } private class AlbumViewPagerAdapter extends FragmentStatePagerAdapter { - private final List fragmentRefs = new ArrayList<>(); AlbumViewPagerAdapter(FragmentManager m) { super(m, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT); } @@ -376,9 +374,7 @@ private class AlbumViewPagerAdapter extends FragmentStatePagerAdapter { public Fragment getItem(int i) { if (SettingValues.oldSwipeMode) { if (i == 0) { - Fragment blank = new BlankFragment(); - fragmentRefs.add(blank); - return blank; + return new BlankFragment(); } i--; } @@ -392,15 +388,9 @@ public Fragment getItem(int i) { Bundle args = new Bundle(); args.putInt("page", i); f.setArguments(args); - fragmentRefs.add(f); return f; } - public Fragment getFragment(int pos) { - if (pos >= 0 && pos < fragmentRefs.size()) return fragmentRefs.get(pos); - return null; - } - @Override public int getCount() { if (images == null) { From 23d7508fff0de6462962129a98903d27da209fab Mon Sep 17 00:00:00 2001 From: PrimaryVision <155949587+PrimaryVision@users.noreply.github.com> Date: Tue, 20 May 2025 01:18:59 +0530 Subject: [PATCH 50/99] Move smaller content tag to upper right corner --- app/src/main/res/layout/header_image_title_view.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/layout/header_image_title_view.xml b/app/src/main/res/layout/header_image_title_view.xml index e3a688a40..83b79addd 100644 --- a/app/src/main/res/layout/header_image_title_view.xml +++ b/app/src/main/res/layout/header_image_title_view.xml @@ -21,7 +21,7 @@ android:layout_width="wrap_content" android:text="IMAGE" android:id="@+id/tag" - android:layout_alignBottom="@+id/leadimage" + android:layout_alignParentTop="true" android:textAllCaps="true" android:layout_alignParentRight="true" android:alpha=".86" From 8fa87ae10237ca52f3631708da5dcfa2df77f728 Mon Sep 17 00:00:00 2001 From: Nathan Grennan Date: Mon, 19 May 2025 14:45:29 -0700 Subject: [PATCH 51/99] Fixed the cascade edge effect when dragging the current view --- .../edgan/redditslide/Activities/Album.java | 129 +++++++----------- .../redditslide/Activities/AlbumPager.java | 5 + .../Activities/CommentsScreen.java | 9 +- .../Activities/CommentsScreenSingle.java | 8 +- .../redditslide/Activities/MediaView.java | 7 + .../redditslide/Activities/RedditGallery.java | 11 +- .../Activities/RedditGalleryPager.java | 8 ++ .../redditslide/Activities/SubredditView.java | 13 +- .../edgan/redditslide/Activities/Tumblr.java | 10 +- .../redditslide/Activities/TumblrPager.java | 8 ++ 10 files changed, 113 insertions(+), 95 deletions(-) diff --git a/app/src/main/java/me/edgan/redditslide/Activities/Album.java b/app/src/main/java/me/edgan/redditslide/Activities/Album.java index 187187d1d..b46b11611 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/Album.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/Album.java @@ -7,6 +7,7 @@ import android.graphics.Color; import android.os.AsyncTask; import android.os.Bundle; +import android.util.TypedValue; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; @@ -33,7 +34,6 @@ import me.edgan.redditslide.Views.PreCachingLayoutManager; import me.edgan.redditslide.Views.ToolbarColorizeHelper; import me.edgan.redditslide.Visuals.ColorPreferences; -import me.edgan.redditslide.Visuals.Palette; import me.edgan.redditslide.util.DialogUtil; import me.edgan.redditslide.util.ImageSaveUtils; import me.edgan.redditslide.util.LinkUtil; @@ -62,20 +62,22 @@ public boolean onOptionsItemSelected(MenuItem item) { if (id == android.R.id.home) { onBackPressed(); } + if (id == R.id.slider) { SettingValues.albumSwipe = true; SettingValues.prefs.edit().putBoolean(SettingValues.PREF_ALBUM_SWIPE, true).apply(); Intent i = new Intent(Album.this, AlbumPager.class); int adapterPosition = getIntent().getIntExtra(MediaView.ADAPTER_POSITION, -1); i.putExtra(MediaView.ADAPTER_POSITION, adapterPosition); + if (getIntent().hasExtra(MediaView.SUBMISSION_URL)) { - i.putExtra( - MediaView.SUBMISSION_URL, - getIntent().getStringExtra(MediaView.SUBMISSION_URL)); + i.putExtra(MediaView.SUBMISSION_URL, getIntent().getStringExtra(MediaView.SUBMISSION_URL)); } + if (submissionTitle != null) { i.putExtra(EXTRA_SUBMISSION_TITLE, submissionTitle); } + i.putExtra("url", url); startActivity(i); finish(); @@ -102,15 +104,7 @@ public boolean onOptionsItemSelected(MenuItem item) { } public void doImageSave(boolean isGif, String contentUrl, int index) { - ImageSaveUtils.doImageSave( - this, - isGif, - contentUrl, - index, - subreddit, - submissionTitle, - this::showFirstDialog - ); + ImageSaveUtils.doImageSave(this, isGif, contentUrl, index, subreddit, submissionTitle, this::showFirstDialog); } public String url; @@ -122,9 +116,11 @@ public boolean onCreateOptionsMenu(Menu menu) { MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.album_vertical, menu); adapterPosition = getIntent().getIntExtra(MediaView.ADAPTER_POSITION, -1); + if (adapterPosition < 0) { menu.findItem(R.id.comments).setVisible(false); } + return true; } @@ -133,11 +129,7 @@ public boolean onCreateOptionsMenu(Menu menu) { public void onCreate(Bundle savedInstanceState) { overrideSwipeFromAnywhere(); super.onCreate(savedInstanceState); - getTheme() - .applyStyle( - new ColorPreferences(this) - .getDarkThemeSubreddit(ColorPreferences.FONT_STYLE), - true); + getTheme().applyStyle(new ColorPreferences(this).getDarkThemeSubreddit(ColorPreferences.FONT_STYLE), true); setContentView(R.layout.album); // Keep the screen on @@ -146,6 +138,7 @@ public void onCreate(Bundle savedInstanceState) { if (getIntent().hasExtra(SUBREDDIT)) { this.subreddit = getIntent().getExtras().getString(SUBREDDIT); } + if (getIntent().hasExtra(EXTRA_SUBMISSION_TITLE)) { this.submissionTitle = getIntent().getExtras().getString(EXTRA_SUBMISSION_TITLE); } @@ -154,25 +147,27 @@ public void onCreate(Bundle savedInstanceState) { album = new AlbumPagerAdapter(getSupportFragmentManager()); pager.setAdapter(album); + pager.setCurrentItem(1); if (SettingValues.oldSwipeMode) { + // Set an opaque background for the ViewPager + TypedValue typedValue = new TypedValue(); + getTheme().resolveAttribute(R.attr.card_background, typedValue, true); + pager.setBackgroundColor(typedValue.data); pager.addOnPageChangeListener( new ViewPager.SimpleOnPageChangeListener() { @Override - public void onPageScrolled( - int position, float positionOffset, int positionOffsetPixels) { + public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { if (position == 0 && positionOffsetPixels == 0) { finish(); } + if (position == 0 && ((AlbumPagerAdapter) pager.getAdapter()).blankPage != null) { if (((AlbumPagerAdapter) pager.getAdapter()).blankPage != null) { ((AlbumPagerAdapter) pager.getAdapter()) .blankPage.doOffset(positionOffset); } - ((AlbumPagerAdapter) pager.getAdapter()) - .blankPage.realBack.setBackgroundColor( - Palette.adjustAlpha(positionOffset * 0.7f)); } } }); @@ -225,26 +220,19 @@ public View onCreateView( LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { rootView = inflater.inflate(R.layout.fragment_verticalalbum, container, false); - final PreCachingLayoutManager mLayoutManager = - new PreCachingLayoutManager(getActivity()); + final PreCachingLayoutManager mLayoutManager = new PreCachingLayoutManager(getActivity()); recyclerView = rootView.findViewById(R.id.images); recyclerView.setLayoutManager(mLayoutManager); - ((Album) getActivity()).url = - getActivity().getIntent().getExtras().getString(EXTRA_URL, ""); + ((Album) getActivity()).url = getActivity().getIntent().getExtras().getString(EXTRA_URL, ""); - new LoadIntoRecycler(((Album) getActivity()).url, getActivity()) - .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + new LoadIntoRecycler(((Album) getActivity()).url, getActivity()).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); ((Album) getActivity()).mToolbar = rootView.findViewById(R.id.toolbar); ((Album) getActivity()).mToolbar.setTitle(R.string.type_album); - ToolbarColorizeHelper.colorizeToolbar( - ((Album) getActivity()).mToolbar, Color.WHITE, (getActivity())); + ToolbarColorizeHelper.colorizeToolbar(((Album) getActivity()).mToolbar, Color.WHITE, (getActivity())); ((Album) getActivity()).setSupportActionBar(((Album) getActivity()).mToolbar); ((Album) getActivity()).getSupportActionBar().setDisplayHomeAsUpEnabled(true); - ((Album) getActivity()) - .mToolbar.setPopupTheme( - new ColorPreferences(getActivity()) - .getDarkThemeSubreddit(ColorPreferences.FONT_STYLE)); + ((Album) getActivity()).mToolbar.setPopupTheme(new ColorPreferences(getActivity()).getDarkThemeSubreddit(ColorPreferences.FONT_STYLE)); return rootView; } @@ -260,40 +248,29 @@ public LoadIntoRecycler(@NonNull String url, @NonNull Activity baseActivity) { @Override public void onError() { if (getActivity() != null) { - getActivity() - .runOnUiThread( - new Runnable() { - @Override - public void run() { - try { - new AlertDialog.Builder(getActivity()) - .setTitle(R.string.error_album_not_found) - .setMessage( - R.string.error_album_not_found_text) - .setNegativeButton( - R.string.btn_no, - (dialog, which) -> - getActivity().finish()) - .setCancelable(false) - .setPositiveButton( - R.string.btn_yes, - (dialog, which) -> { - Intent i = - new Intent( - getActivity(), - Website.class); - i.putExtra( - LinkUtil.EXTRA_URL, - url); - startActivity(i); - getActivity().finish(); - }) - .show(); - } catch (Exception e) { - - } - } - }); + getActivity().runOnUiThread( + new Runnable() { + @Override + public void run() { + try { + new AlertDialog.Builder(getActivity()) + .setTitle(R.string.error_album_not_found) + .setMessage(R.string.error_album_not_found_text) + .setNegativeButton(R.string.btn_no, (dialog, which) -> getActivity().finish()) + .setCancelable(false) + .setPositiveButton( + R.string.btn_yes, + (dialog, which) -> { + Intent i = new Intent(getActivity(), Website.class); + i.putExtra(LinkUtil.EXTRA_URL, url); + startActivity(i); + getActivity().finish(); + }) + .show(); + } catch (Exception e) {} + } + } + ); } } @@ -304,13 +281,13 @@ public void doWithData(final List jsonElements) { getActivity().findViewById(R.id.progress).setVisibility(View.GONE); Album albumActivity = (Album) getActivity(); albumActivity.images = new ArrayList<>(jsonElements); - AlbumView adapter = - new AlbumView( - baseActivity, - albumActivity.images, - getActivity().findViewById(R.id.toolbar).getHeight(), - albumActivity.subreddit, - albumActivity.submissionTitle); + AlbumView adapter = new AlbumView( + baseActivity, + albumActivity.images, + getActivity().findViewById(R.id.toolbar).getHeight(), + albumActivity.subreddit, + albumActivity.submissionTitle + ); recyclerView.setAdapter(adapter); } } diff --git a/app/src/main/java/me/edgan/redditslide/Activities/AlbumPager.java b/app/src/main/java/me/edgan/redditslide/Activities/AlbumPager.java index 6c70065f7..ef8f8071b 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/AlbumPager.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/AlbumPager.java @@ -15,6 +15,7 @@ import android.os.AsyncTask; import android.os.Bundle; import android.os.Handler; +import android.util.TypedValue; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; @@ -255,6 +256,10 @@ public void run() { if (SettingValues.oldSwipeMode) { startPage = 1; + // Set an opaque background for the ViewPager + TypedValue typedValue = new TypedValue(); + getTheme().resolveAttribute(R.attr.card_background, typedValue, true); + p.setBackgroundColor(typedValue.data); } p.setCurrentItem(startPage); diff --git a/app/src/main/java/me/edgan/redditslide/Activities/CommentsScreen.java b/app/src/main/java/me/edgan/redditslide/Activities/CommentsScreen.java index a0877d2ce..3a79e457b 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/CommentsScreen.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/CommentsScreen.java @@ -28,7 +28,6 @@ import me.edgan.redditslide.R; import me.edgan.redditslide.Reddit; import me.edgan.redditslide.SettingValues; -import me.edgan.redditslide.Visuals.Palette; import me.edgan.redditslide.util.CustomViewPager; import me.edgan.redditslide.util.KeyboardUtil; @@ -38,6 +37,8 @@ import java.util.List; import java.util.Locale; +import android.util.TypedValue; + /** * This activity is responsible for the view when clicking on a post, showing the post and its * comments underneath with the slide left/right for the next post. @@ -207,6 +208,7 @@ public void onCreate(Bundle savedInstance) { comments = new CommentsScreenPagerAdapter(getSupportFragmentManager()); pager.setAdapter(comments); + currentPage = firstPage; if (SettingValues.oldSwipeMode) { @@ -218,6 +220,10 @@ public void onCreate(Bundle savedInstance) { pager.setEntryPageIndex(firstPage); if (SettingValues.oldSwipeMode) { + // Set an opaque background for the ViewPager + TypedValue typedValue = new TypedValue(); + getTheme().resolveAttribute(R.attr.card_background, typedValue, true); + pager.setBackgroundColor(typedValue.data); pager.addOnPageChangeListener(new CommonPageChangeListener() { @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { @@ -229,7 +235,6 @@ public void onPageScrolled(int position, float positionOffset, int positionOffse if (adapter.blankPage != null) { adapter.blankPage.doOffset(positionOffset); } - pager.setBackgroundColor(Palette.adjustAlpha(positionOffset * 0.7f)); } } }); diff --git a/app/src/main/java/me/edgan/redditslide/Activities/CommentsScreenSingle.java b/app/src/main/java/me/edgan/redditslide/Activities/CommentsScreenSingle.java index 3d7f5b9c0..a217742ef 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/CommentsScreenSingle.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/CommentsScreenSingle.java @@ -32,7 +32,6 @@ import me.edgan.redditslide.SettingValues; import me.edgan.redditslide.SwipeLayout.Utils; import me.edgan.redditslide.UserSubscriptions; -import me.edgan.redditslide.Visuals.Palette; import me.edgan.redditslide.util.LogUtil; import net.dean.jraw.models.Submission; @@ -186,10 +185,13 @@ private void setupAdapter() { pager = (ViewPager) findViewById(R.id.content_view); comments = new CommentsScreenSinglePagerAdapter(getSupportFragmentManager()); pager.setAdapter(comments); - pager.setBackgroundColor(Color.TRANSPARENT); pager.setCurrentItem(1); if (SettingValues.oldSwipeMode) { + TypedValue typedValue = new TypedValue(); + getTheme().resolveAttribute(R.attr.card_background, typedValue, true); + pager.setBackgroundColor(typedValue.data); + pager.addOnPageChangeListener(new CommonPageChangeListener() { @Override public void onPageScrolled( @@ -203,8 +205,6 @@ public void onPageScrolled( != null) { ((CommentsScreenSinglePagerAdapter) pager.getAdapter()) .blankPage.doOffset(positionOffset); - pager.setBackgroundColor( - Palette.adjustAlpha(positionOffset * 0.7f)); } } }); diff --git a/app/src/main/java/me/edgan/redditslide/Activities/MediaView.java b/app/src/main/java/me/edgan/redditslide/Activities/MediaView.java index 22324efdd..80570bf14 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/MediaView.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/MediaView.java @@ -17,6 +17,7 @@ import android.os.Bundle; import android.os.Handler; import android.util.Log; +import android.util.TypedValue; import android.view.View; import android.view.WindowManager; import android.widget.ImageView; @@ -445,6 +446,12 @@ public void onCreate(Bundle savedInstanceState) { setContentView(R.layout.activity_media); + if (SettingValues.oldSwipeMode) { + TypedValue typedValue = new TypedValue(); + getTheme().resolveAttribute(R.attr.card_background, typedValue, true); + getWindow().getDecorView().setBackgroundColor(typedValue.data); + } + // Keep the screen on getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); diff --git a/app/src/main/java/me/edgan/redditslide/Activities/RedditGallery.java b/app/src/main/java/me/edgan/redditslide/Activities/RedditGallery.java index dccdfcc0c..e6fb98292 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/RedditGallery.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/RedditGallery.java @@ -6,6 +6,7 @@ import android.graphics.Color; import android.net.Uri; import android.os.Bundle; +import android.util.TypedValue; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; @@ -32,7 +33,6 @@ import me.edgan.redditslide.Views.PreCachingLayoutManager; import me.edgan.redditslide.Views.ToolbarColorizeHelper; import me.edgan.redditslide.Visuals.ColorPreferences; -import me.edgan.redditslide.Visuals.Palette; import me.edgan.redditslide.util.DialogUtil; import me.edgan.redditslide.util.GifUtils; import me.edgan.redditslide.util.ImageSaveUtils; @@ -186,6 +186,11 @@ public void onCreate(Bundle savedInstanceState) { pager.setAdapter(gallery); pager.setCurrentItem(1); if (SettingValues.oldSwipeMode) { + // Set an opaque background for the ViewPager + TypedValue typedValue = new TypedValue(); + getTheme().resolveAttribute(R.attr.card_background, typedValue, true); + pager.setBackgroundColor(typedValue.data); + pager.addOnPageChangeListener( new ViewPager.SimpleOnPageChangeListener() { @Override @@ -198,10 +203,6 @@ public void onPageScrolled( if (((RedditGalleryPagerAdapter) pager.getAdapter()).blankPage != null) { ((RedditGalleryPagerAdapter) pager.getAdapter()).blankPage.doOffset(positionOffset); } - - ((RedditGalleryPagerAdapter) pager - .getAdapter()).blankPage.realBack - .setBackgroundColor(Palette.adjustAlpha(positionOffset * 0.7f)); } } } diff --git a/app/src/main/java/me/edgan/redditslide/Activities/RedditGalleryPager.java b/app/src/main/java/me/edgan/redditslide/Activities/RedditGalleryPager.java index 3e8a088d3..da1816237 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/RedditGalleryPager.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/RedditGalleryPager.java @@ -8,6 +8,7 @@ import android.net.Uri; import android.os.Bundle; import android.util.Log; +import android.util.TypedValue; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; @@ -196,6 +197,13 @@ public void onCreate(Bundle savedInstanceState) { GalleryViewPagerAdapter adapter = new GalleryViewPagerAdapter(getSupportFragmentManager()); p.setAdapter(adapter); + if (SettingValues.oldSwipeMode) { + // Set an opaque background for the ViewPager + TypedValue typedValue = new TypedValue(); + getTheme().resolveAttribute(R.attr.card_background, typedValue, true); + p.setBackgroundColor(typedValue.data); + } + p.post( new Runnable() { @Override diff --git a/app/src/main/java/me/edgan/redditslide/Activities/SubredditView.java b/app/src/main/java/me/edgan/redditslide/Activities/SubredditView.java index b06b292e5..4ee113606 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/SubredditView.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/SubredditView.java @@ -13,6 +13,7 @@ import android.os.Parcelable; import android.text.Spannable; import android.util.Log; +import android.util.TypedValue; import android.view.Gravity; import android.view.LayoutInflater; import android.view.Menu; @@ -198,6 +199,14 @@ public void onCreate(Bundle savedInstanceState) { } pager.setAdapter(adapter); pager.setCurrentItem(1); + + if (SettingValues.oldSwipeMode) { + // Set an opaque background for the ViewPager + TypedValue typedValue = new TypedValue(); + getTheme().resolveAttribute(R.attr.card_background, typedValue, true); + pager.setBackgroundColor(typedValue.data); + } + mToolbar.setOnClickListener( new View.OnClickListener() { @Override @@ -1920,8 +1929,6 @@ public void onPageScrolled( ((SubredditPagerAdapter) pager.getAdapter()) .blankPage.doOffset(positionOffset); - pager.setBackgroundColor( - Palette.adjustAlpha(positionOffset * 0.7f)); } } }); @@ -2041,7 +2048,6 @@ private void handleThreeButtonScrolling(int position, float positionOffset, int overridePendingTransition(0, R.anim.fade_out); } blankPage.doOffset(positionOffset); - pager.setBackgroundColor(Palette.adjustAlpha(positionOffset * 0.7f)); } else if (positionOffset == 0) { handlePositionOffset(position); } @@ -2054,7 +2060,6 @@ private void handleDefaultScrolling(int position, float positionOffset) { } private void handlePositionOffset(int position) { - if ((position == 0) || (position == 1)) { doPageSelectedComments(position); } else { diff --git a/app/src/main/java/me/edgan/redditslide/Activities/Tumblr.java b/app/src/main/java/me/edgan/redditslide/Activities/Tumblr.java index b9372f212..179a89f48 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/Tumblr.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/Tumblr.java @@ -5,6 +5,7 @@ import android.graphics.Color; import android.os.AsyncTask; import android.os.Bundle; +import android.util.TypedValue; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; @@ -30,7 +31,6 @@ import me.edgan.redditslide.Views.PreCachingLayoutManager; import me.edgan.redditslide.Views.ToolbarColorizeHelper; import me.edgan.redditslide.Visuals.ColorPreferences; -import me.edgan.redditslide.Visuals.Palette; import me.edgan.redditslide.util.DialogUtil; import me.edgan.redditslide.util.ImageSaveUtils; import me.edgan.redditslide.util.LinkUtil; @@ -142,6 +142,11 @@ public void onCreate(Bundle savedInstanceState) { } if (SettingValues.oldSwipeMode) { + // Set an opaque background for the ViewPager + TypedValue typedValue = new TypedValue(); + getTheme().resolveAttribute(R.attr.card_background, typedValue, true); + pager.setBackgroundColor(typedValue.data); + pager.addOnPageChangeListener( new ViewPager.SimpleOnPageChangeListener() { @Override @@ -157,9 +162,6 @@ public void onPageScrolled( ((TumblrPagerAdapter) pager.getAdapter()) .blankPage.doOffset(positionOffset); } - ((TumblrPagerAdapter) pager.getAdapter()) - .blankPage.realBack.setBackgroundColor( - Palette.adjustAlpha(positionOffset * 0.7f)); } } }); diff --git a/app/src/main/java/me/edgan/redditslide/Activities/TumblrPager.java b/app/src/main/java/me/edgan/redditslide/Activities/TumblrPager.java index a2acf6068..a83dd222b 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/TumblrPager.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/TumblrPager.java @@ -12,6 +12,7 @@ import android.os.AsyncTask; import android.os.Bundle; import android.util.Log; +import android.util.TypedValue; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; @@ -214,6 +215,13 @@ public void doWithData(final List jsonElements) { new TumblrViewPagerAdapter(getSupportFragmentManager()); p.setAdapter(adapter); + if (SettingValues.oldSwipeMode) { + // Set an opaque background for the ViewPager + TypedValue typedValue = new TypedValue(); + getTheme().resolveAttribute(R.attr.card_background, typedValue, true); + p.setBackgroundColor(typedValue.data); + } + int startPage = 0; if (SettingValues.oldSwipeMode) { From 48e2a5a70ba2c0aa748603cac2d4fdb69fa9d902 Mon Sep 17 00:00:00 2001 From: Nathan Grennan Date: Mon, 19 May 2025 22:33:58 -0700 Subject: [PATCH 52/99] =?UTF-8?q?Fix=20for=20Bug:=20Some=20Tumblr=20GIFs?= =?UTF-8?q?=20don=E2=80=99t=20open=20in=20MediaView=20=20#230?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../SubmissionViews/PopulateSubmissionViewHolder.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/java/me/edgan/redditslide/SubmissionViews/PopulateSubmissionViewHolder.java b/app/src/main/java/me/edgan/redditslide/SubmissionViews/PopulateSubmissionViewHolder.java index f2744549a..088e184f9 100644 --- a/app/src/main/java/me/edgan/redditslide/SubmissionViews/PopulateSubmissionViewHolder.java +++ b/app/src/main/java/me/edgan/redditslide/SubmissionViews/PopulateSubmissionViewHolder.java @@ -552,6 +552,8 @@ public static void openGif( .replace("&", "&")); } else if (t.shouldLoadPreview() && submission.getDataNode().has("preview") + && submission.getDataNode().get("preview").has("reddit_video_preview") + && submission.getDataNode().get("preview").get("reddit_video_preview").has("fallback_url") && submission .getDataNode() .get("preview") From a88d9b1914e17df39be7d97f996f389dd01fee5b Mon Sep 17 00:00:00 2001 From: Nathan Grennan Date: Tue, 20 May 2025 00:02:12 -0700 Subject: [PATCH 53/99] Fixed Bug: Tumblr album with GIFs loads multiple previews at once in horizontal mode #231 --- .../edgan/redditslide/Activities/Tumblr.java | 2 +- .../redditslide/Activities/TumblrPager.java | 143 ++++++++++++++---- .../edgan/redditslide/Tumblr/TumblrUtils.java | 11 +- .../edgan/redditslide/util/GifDrawable.java | 10 ++ .../me/edgan/redditslide/util/GifUtils.java | 5 + 5 files changed, 137 insertions(+), 34 deletions(-) diff --git a/app/src/main/java/me/edgan/redditslide/Activities/Tumblr.java b/app/src/main/java/me/edgan/redditslide/Activities/Tumblr.java index b9372f212..e2a543a90 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/Tumblr.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/Tumblr.java @@ -222,7 +222,7 @@ public View onCreateView( new LoadIntoRecycler(((Tumblr) getActivity()).url, getActivity()) .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); ((Tumblr) getActivity()).mToolbar = rootView.findViewById(R.id.toolbar); - ((Tumblr) getActivity()).mToolbar.setTitle(R.string.type_album); + ((Tumblr) getActivity()).mToolbar.setTitle(R.string.type_tumblr); ToolbarColorizeHelper.colorizeToolbar( ((Tumblr) getActivity()).mToolbar, Color.WHITE, (getActivity())); ((Tumblr) getActivity()).setSupportActionBar(((Tumblr) getActivity()).mToolbar); diff --git a/app/src/main/java/me/edgan/redditslide/Activities/TumblrPager.java b/app/src/main/java/me/edgan/redditslide/Activities/TumblrPager.java index a2acf6068..9d7060a84 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/TumblrPager.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/TumblrPager.java @@ -11,6 +11,7 @@ import android.graphics.drawable.Drawable; import android.os.AsyncTask; import android.os.Bundle; +import android.os.SystemClock; import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; @@ -24,6 +25,7 @@ import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ProgressBar; +import android.widget.RelativeLayout; import android.widget.TextView; import androidx.annotation.NonNull; @@ -63,6 +65,7 @@ import me.edgan.redditslide.util.BlendModeUtil; import me.edgan.redditslide.util.DialogUtil; import me.edgan.redditslide.util.FileUtil; +import me.edgan.redditslide.util.GifDrawable; import me.edgan.redditslide.util.GifUtils; import me.edgan.redditslide.util.ImageSaveUtils; import me.edgan.redditslide.util.LinkUtil; @@ -70,6 +73,10 @@ import me.edgan.redditslide.util.ShareUtil; import me.edgan.redditslide.util.SubmissionParser; +import java.io.File; +import android.graphics.Movie; +import android.net.Uri; + import java.net.URI; import java.net.URISyntaxException; import java.util.ArrayList; @@ -160,7 +167,7 @@ public void onCreate(Bundle savedInstanceState) { getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); mToolbar = (Toolbar) findViewById(R.id.toolbar); - mToolbar.setTitle(R.string.type_album); + mToolbar.setTitle(R.string.type_tumblr); ToolbarColorizeHelper.colorizeToolbar(mToolbar, Color.WHITE, this); setSupportActionBar(mToolbar); getSupportActionBar().setDisplayHomeAsUpEnabled(true); @@ -394,33 +401,117 @@ public void setUserVisibleHint(boolean isVisibleToUser) { } @Override - public View onCreateView( - LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - rootView = - (ViewGroup) - inflater.inflate(R.layout.submission_gifcard_album, container, false); + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + Bundle bundle = this.getArguments(); + final int i = bundle.getInt("page", 0); + + rootView = (ViewGroup) inflater.inflate(R.layout.submission_gifcard_album, container, false); loader = rootView.findViewById(R.id.gifprogress); + final View videoView = rootView.findViewById(R.id.gif); // This is an ExoVideoView + + final String url = ((TumblrPager) getActivity()).images.get(i).getOriginalSize().getUrl(); + + if (url != null && url.toLowerCase().endsWith(".gif")) { + videoView.setVisibility(View.GONE); // Hide ExoVideoView + View playButton = rootView.findViewById(R.id.playbutton); + if (playButton != null) { + playButton.setVisibility(View.GONE); + } + + final ImageView imageView = new ImageView(getContext()); + imageView.setScaleType(ImageView.ScaleType.FIT_CENTER); + RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams( + RelativeLayout.LayoutParams.MATCH_PARENT, + RelativeLayout.LayoutParams.MATCH_PARENT); + layoutParams.addRule(RelativeLayout.CENTER_IN_PARENT, RelativeLayout.TRUE); + imageView.setLayoutParams(layoutParams); + + RelativeLayout imageArea = rootView.findViewById(R.id.imagearea); + imageArea.addView(imageView); // Add ImageView to the layout + + loader.setVisibility(View.VISIBLE); + + GifUtils.downloadGif(url, new GifUtils.GifDownloadCallback() { + @Override + public void onGifDownloaded(File gifFile) { + if (getActivity() == null) return; + getActivity().runOnUiThread(new Runnable() { + @Override + public void run() { + loader.setVisibility(View.GONE); + Movie movie = Movie.decodeFile(gifFile.getAbsolutePath()); + if (movie != null) { + GifDrawable gifDrawable = new GifDrawable(movie, new Drawable.Callback() { + @Override + public void invalidateDrawable(@NonNull Drawable who) { + imageView.invalidate(); + } + + @Override + public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when) { + imageView.postDelayed(what, when - SystemClock.uptimeMillis()); + } + + @Override + public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what) { + imageView.removeCallbacks(what); + } + }); + imageView.setImageDrawable(gifDrawable); + gifDrawable.start(); + } else { + // Optionally, show an error or fallback + Log.e(TAG, "Failed to decode GIF: " + url); + if (videoView instanceof ExoVideoView) { + ((ExoVideoView) videoView).setVideoURI(Uri.parse(url), ExoVideoView.VideoType.STANDARD, null); // Fallback to ExoVideoView if Movie decoding fails + ((ExoVideoView) videoView).play(); + videoView.setVisibility(View.VISIBLE); + imageView.setVisibility(View.GONE); + } + } + } + }); + } + + @Override + public void onGifDownloadFailed(Exception e) { + if (getActivity() == null) return; + getActivity().runOnUiThread(new Runnable() { + @Override + public void run() { + loader.setVisibility(View.GONE); + Log.e(TAG, "Failed to download GIF: " + url, e); + // Fallback to trying with ExoVideoView or show error + if (videoView instanceof ExoVideoView) { + ((ExoVideoView) videoView).setVideoURI(Uri.parse(url), ExoVideoView.VideoType.STANDARD, null); + ((ExoVideoView) videoView).play(); + videoView.setVisibility(View.VISIBLE); + imageView.setVisibility(View.GONE); + } + } + }); + } + }, getContext(), null); // Pass null for submissionTitle if not available/needed here + + } else { // Not a direct .gif URL, or URL is null, proceed with ExoVideoView + gif = rootView.findViewById(R.id.gif); + gif.setVisibility(View.VISIBLE); + final ExoVideoView v = (ExoVideoView) gif; + v.clearFocus(); + + new GifUtils.AsyncLoadGif( + getActivity(), + rootView.findViewById(R.id.gif), // This is the ExoVideoView + loader, + null, // placeholder + false, // closeIfNull + true, // autostart + rootView.findViewById(R.id.size), + ((TumblrPager) getActivity()).subreddit, + null) // Pass null for submissionTitle + .execute(url); + } - gif = rootView.findViewById(R.id.gif); - - gif.setVisibility(View.VISIBLE); - final ExoVideoView v = (ExoVideoView) gif; - v.clearFocus(); - - final String url = - ((TumblrPager) getActivity()).images.get(i).getOriginalSize().getUrl(); - - new GifUtils.AsyncLoadGif( - getActivity(), - rootView.findViewById(R.id.gif), - loader, - null, // placeholder - false, // closeIfNull - true, // autostart - rootView.findViewById(R.id.size), - ((TumblrPager) getActivity()).subreddit, - null) - .execute(url); rootView.findViewById(R.id.more) .setOnClickListener( new View.OnClickListener() { diff --git a/app/src/main/java/me/edgan/redditslide/Tumblr/TumblrUtils.java b/app/src/main/java/me/edgan/redditslide/Tumblr/TumblrUtils.java index 7fa89e490..696d12b4d 100644 --- a/app/src/main/java/me/edgan/redditslide/Tumblr/TumblrUtils.java +++ b/app/src/main/java/me/edgan/redditslide/Tumblr/TumblrUtils.java @@ -152,13 +152,9 @@ protected ArrayList doInBackground(final String... sub) { + "&id=" + id; LogUtil.v(apiUrl); - if (tumblrRequests.contains(apiUrl) - && JsonParser.parseString(tumblrRequests.getString(apiUrl, "")) - .getAsJsonObject() - .has("response")) { - parseJson( - JsonParser.parseString(tumblrRequests.getString(apiUrl, "")) - .getAsJsonObject()); + if (tumblrRequests.contains(apiUrl) && JsonParser.parseString(tumblrRequests.getString(apiUrl, "")).getAsJsonObject().has("response")) { + Log.d(TAG, "parseJson: 1" + tumblrRequests.getString(apiUrl, "")); + parseJson(JsonParser.parseString(tumblrRequests.getString(apiUrl, "")).getAsJsonObject()); } else { LogUtil.v(apiUrl); final JsonObject result = HttpUtil.getJsonObject(client, gson, apiUrl); @@ -172,6 +168,7 @@ protected ArrayList doInBackground(final String... sub) { .get(0) .getAsJsonObject() .has("photos")) { + Log.d(TAG, "parseJson: 2" + result.toString()); tumblrRequests.edit().putString(apiUrl, result.toString()).apply(); parseJson(result); } else { diff --git a/app/src/main/java/me/edgan/redditslide/util/GifDrawable.java b/app/src/main/java/me/edgan/redditslide/util/GifDrawable.java index 4f75b37ce..db52849f7 100644 --- a/app/src/main/java/me/edgan/redditslide/util/GifDrawable.java +++ b/app/src/main/java/me/edgan/redditslide/util/GifDrawable.java @@ -59,6 +59,16 @@ public void setColorFilter(ColorFilter colorFilter) { invalidateSelf(); } + @Override + public int getIntrinsicWidth() { + return movie != null ? movie.width() : 0; + } + + @Override + public int getIntrinsicHeight() { + return movie != null ? movie.height() : 0; + } + @Override public int getOpacity() { return PixelFormat.TRANSLUCENT; diff --git a/app/src/main/java/me/edgan/redditslide/util/GifUtils.java b/app/src/main/java/me/edgan/redditslide/util/GifUtils.java index 3e7b34417..ba41f0f23 100644 --- a/app/src/main/java/me/edgan/redditslide/util/GifUtils.java +++ b/app/src/main/java/me/edgan/redditslide/util/GifUtils.java @@ -580,6 +580,7 @@ protected void onPreExecute() { public enum VideoType { IMGUR, STREAMABLE, + TUMBLR, GFYCAT, DIRECT, OTHER, @@ -662,6 +663,8 @@ public static VideoType getVideoType(String url) { if (realURL.contains("streamable.com")) return VideoType.STREAMABLE; + if (realURL.contains("tumblr.com")) return VideoType.TUMBLR; + return VideoType.OTHER; } @@ -924,6 +927,8 @@ protected Uri doInBackground(String... sub) { case REDDIT_GALLERY: return Uri.parse(url); case DIRECT: + case TUMBLR: + return Uri.parse(url); case IMGUR: try { return Uri.parse(url); From bb5da480d7cbaeeb5e861e2effd59c045cb7ac1a Mon Sep 17 00:00:00 2001 From: Nathan Grennan Date: Tue, 20 May 2025 13:32:55 -0700 Subject: [PATCH 54/99] Fixed WindowManager in MediaView.java --- app/src/main/java/me/edgan/redditslide/Activities/MediaView.java | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/java/me/edgan/redditslide/Activities/MediaView.java b/app/src/main/java/me/edgan/redditslide/Activities/MediaView.java index aeb4e9d40..1a8fe9855 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/MediaView.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/MediaView.java @@ -19,6 +19,7 @@ import android.util.Log; import android.util.TypedValue; import android.view.View; +import android.view.WindowManager; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ProgressBar; From a1e3aba893a64ee7ff030b20cee48d6bff5997f1 Mon Sep 17 00:00:00 2001 From: Nathan Grennan Date: Tue, 20 May 2025 22:23:03 -0700 Subject: [PATCH 55/99] Fixed Tumblr album with GIFs doesn't load loads properly in vertical mode #237 Fixed A single Tumblr GIF doesn't load #238 --- .../redditslide/Activities/MediaView.java | 181 ++++++++-- .../redditslide/Adapters/TumblrView.java | 310 +++++++++++++----- .../me/edgan/redditslide/util/GifUtils.java | 38 ++- app/src/main/res/layout/activity_media.xml | 9 + .../main/res/layout/list_item_tumblr_gif.xml | 33 ++ 5 files changed, 453 insertions(+), 118 deletions(-) create mode 100644 app/src/main/res/layout/list_item_tumblr_gif.xml diff --git a/app/src/main/java/me/edgan/redditslide/Activities/MediaView.java b/app/src/main/java/me/edgan/redditslide/Activities/MediaView.java index 79364c0a3..33a071b2b 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/MediaView.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/MediaView.java @@ -16,6 +16,7 @@ import android.os.AsyncTask; import android.os.Bundle; import android.os.Handler; +import android.os.SystemClock; import android.util.Log; import android.view.View; import android.widget.ImageView; @@ -80,6 +81,10 @@ import java.util.List; import java.util.UUID; +import android.graphics.Movie; +import me.edgan.redditslide.util.GifDrawable; +import androidx.annotation.NonNull; + /** Created by ccrama on 3/5/2015. */ public class MediaView extends BaseSaveActivity { public static final String EXTRA_URL = "url"; @@ -112,6 +117,10 @@ public class MediaView extends BaseSaveActivity { private String imgurKey; private String lastContentUrl; + // Fields for direct GIF handling + private ImageView directGifViewer; + private GifDrawable activeGifDrawable; // To manage its lifecycle + private static final String TAG = "MediaView"; private static boolean shouldTruncate(String url) { @@ -372,13 +381,27 @@ public void onScanCompleted(String path, Uri uri) { @Override public void onDestroy() { super.onDestroy(); - ((SubsamplingScaleImageView) findViewById(R.id.submission_image)).recycle(); - if (gif != null) { + if (findViewById(R.id.submission_image) != null && ((SubsamplingScaleImageView) findViewById(R.id.submission_image)) != null) { + ((SubsamplingScaleImageView) findViewById(R.id.submission_image)).recycle(); + } + if (gif != null) { // This is GifUtils.AsyncLoadGif for ExoVideoView gif.cancel(); gif.cancel(true); } + // Cleanup for direct GifDrawable + if (activeGifDrawable != null) { + activeGifDrawable.setCallback(null); + activeGifDrawable.stop(); + activeGifDrawable = null; + } + if (directGifViewer != null) { + directGifViewer.setImageDrawable(null); + } + if (!didLoadGif && fileLoc != null && !fileLoc.isEmpty()) { + // This fileLoc seems related to an old way of handling gifs, review if still needed + // For now, keeping it as is. new File(fileLoc).delete(); } } @@ -443,6 +466,9 @@ public void onCreate(Bundle savedInstanceState) { } setContentView(R.layout.activity_media); + // Initialize the new ImageView for direct GIFs and cast it + directGifViewer = (ImageView) findViewById(R.id.direct_gif_viewer); + // Hide speed button by default ImageView speedBtn = (ImageView) findViewById(R.id.speed); if (speedBtn != null) speedBtn.setVisibility(View.GONE); @@ -595,41 +621,124 @@ public void doLoad(final String contentUrl) { public void doLoadGif(final String dat) { isGif = true; - videoView = (ExoVideoView) findViewById(R.id.gif); - findViewById(R.id.black) - .setOnClickListener( - new View.OnClickListener() { - @Override - public void onClick(View v) { - if (findViewById(R.id.gifheader).getVisibility() == View.GONE) { - AnimatorUtil.animateIn(findViewById(R.id.gifheader), 56); - AnimatorUtil.fadeOut(findViewById(R.id.black)); - } - } - }); - videoView.clearFocus(); - findViewById(R.id.gifarea).setVisibility(View.VISIBLE); - findViewById(R.id.submission_image).setVisibility(View.GONE); final ProgressBar loader = (ProgressBar) findViewById(R.id.gifprogress); - findViewById(R.id.progress).setVisibility(View.GONE); - gif = - new GifUtils.AsyncLoadGif( - this, - videoView, - loader, - findViewById(R.id.placeholder), - true, - true, - ((TextView) findViewById(R.id.size)), - subreddit, - submissionTitle); - // Show and attach speed button for GIFs - ImageView speedBtn = (ImageView) findViewById(R.id.speed); - if (speedBtn != null) speedBtn.setVisibility(View.VISIBLE); - videoView.attachMuteButton((ImageView) findViewById(R.id.mute)); - videoView.attachHqButton((ImageView) findViewById(R.id.hq)); - videoView.attachSpeedButton(speedBtn, this); - gif.execute(dat); + final String gifUrl = GifUtils.AsyncLoadGif.formatUrl(dat); // Corrected static call + + if (gifUrl.toLowerCase().endsWith(".gif")) { + // Handle direct .gif URLs with Movie/GifDrawable + Log.v(TAG, "Loading direct GIF: " + gifUrl); // Changed to Log.v + findViewById(R.id.gifarea).setVisibility(View.VISIBLE); // Ensure gifarea is visible for progress bar + findViewById(R.id.submission_image).setVisibility(View.GONE); + if (videoView != null) videoView.setVisibility(View.GONE); // Hide ExoVideoView + directGifViewer.setVisibility(View.VISIBLE); // Show our ImageView + loader.setVisibility(View.VISIBLE); + loader.setIndeterminate(true); // Indeterminate for download phase + + // Clear any previous direct GIF + if (activeGifDrawable != null) { + activeGifDrawable.setCallback(null); + activeGifDrawable.stop(); + } + directGifViewer.setImageDrawable(null); + + GifUtils.downloadGif(gifUrl, new GifUtils.GifDownloadCallback() { + @Override + public void onGifDownloaded(File gifFile) { + if (isFinishing() || isDestroyed()) return; + runOnUiThread(() -> { + loader.setVisibility(View.GONE); + Movie movie = Movie.decodeFile(gifFile.getAbsolutePath()); + if (movie != null) { + activeGifDrawable = new GifDrawable(movie, new Drawable.Callback() { + @Override + public void invalidateDrawable(@NonNull Drawable who) { + directGifViewer.invalidate(); + } + + @Override + public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when) { + directGifViewer.postDelayed(what, when - SystemClock.uptimeMillis()); + } + + @Override + public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what) { + directGifViewer.removeCallbacks(what); + } + }); + directGifViewer.setImageDrawable(activeGifDrawable); + activeGifDrawable.start(); + didLoadGif = true; // Mark that a GIF was successfully loaded this way + fileLoc = gifFile.getAbsolutePath(); // Potentially for cleanup, though this might need review + } else { + Log.e(TAG, "Failed to decode direct GIF: " + gifUrl); + Toast.makeText(MediaView.this, "Failed to load GIF.", Toast.LENGTH_SHORT).show(); + // Optionally, try to open externally or show a specific error view + finish(); // Or handle error more gracefully + } + }); + } + + @Override + public void onGifDownloadFailed(Exception e) { + if (isFinishing() || isDestroyed()) return; + runOnUiThread(() -> { + loader.setVisibility(View.GONE); + Log.e(TAG, "Failed to download direct GIF: " + gifUrl, e); + Toast.makeText(MediaView.this, "Failed to download GIF.", Toast.LENGTH_SHORT).show(); + finish(); // Or handle error more gracefully + }); + } + }, this, submissionTitle); + + } else { + // Existing logic for Gfycat, Streamable, v.redd.it, etc., using ExoVideoView via AsyncLoadGif + Log.v(TAG, "Loading GIF/video via AsyncLoadGif (ExoPlayer): " + gifUrl); // Changed to Log.v + if (directGifViewer != null) directGifViewer.setVisibility(View.GONE); // Hide our direct ImageViewer + videoView = (ExoVideoView) findViewById(R.id.gif); + videoView.setVisibility(View.VISIBLE); // Ensure ExoVideoView is visible + + findViewById(R.id.black) + .setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View v) { + if (findViewById(R.id.gifheader).getVisibility() == View.GONE) { + AnimatorUtil.animateIn(findViewById(R.id.gifheader), 56); + AnimatorUtil.fadeOut(findViewById(R.id.black)); + } + } + }); + videoView.clearFocus(); + findViewById(R.id.gifarea).setVisibility(View.VISIBLE); + findViewById(R.id.submission_image).setVisibility(View.GONE); + loader.setVisibility(View.VISIBLE); // Progress bar for AsyncLoadGif + findViewById(R.id.progress).setVisibility(View.GONE); // Main progress bar for images + + // Ensure this.gif (AsyncLoadGif) is not mixed up with activeGifDrawable (GifDrawable) + if (this.gif != null) { // Cancel previous AsyncLoadGif if any + this.gif.cancel(true); + } + this.gif = // Assign to the class field 'gif' + new GifUtils.AsyncLoadGif( + this, + videoView, + loader, + findViewById(R.id.placeholder), + true, // closeIfNull + true, // autostart + ((TextView) findViewById(R.id.size)), + subreddit, + submissionTitle); + // Show and attach speed button for GIFs (relevant for ExoVideoView) + ImageView speedBtn = (ImageView) findViewById(R.id.speed); + if (speedBtn != null) speedBtn.setVisibility(View.VISIBLE); + videoView.attachMuteButton((ImageView) findViewById(R.id.mute)); + videoView.attachHqButton((ImageView) findViewById(R.id.hq)); + videoView.attachSpeedButton(speedBtn, this); + this.gif.execute(gifUrl); // Use the formatted gifUrl + } + + // Common setup for both paths (direct GIF or ExoVideoView GIF) findViewById(R.id.more) .setOnClickListener( new View.OnClickListener() { diff --git a/app/src/main/java/me/edgan/redditslide/Adapters/TumblrView.java b/app/src/main/java/me/edgan/redditslide/Adapters/TumblrView.java index de6e868f9..6669e5331 100644 --- a/app/src/main/java/me/edgan/redditslide/Adapters/TumblrView.java +++ b/app/src/main/java/me/edgan/redditslide/Adapters/TumblrView.java @@ -11,6 +11,7 @@ import android.widget.GridView; import android.widget.ImageView; import android.widget.LinearLayout; +import android.widget.ProgressBar; import android.widget.RelativeLayout; import androidx.appcompat.app.AlertDialog; @@ -30,20 +31,38 @@ import me.edgan.redditslide.Visuals.FontPreferences; import me.edgan.redditslide.util.LinkUtil; import me.edgan.redditslide.util.SubmissionParser; +import me.edgan.redditslide.util.GifDrawable; +import me.edgan.redditslide.util.GifUtils; import java.net.URI; import java.net.URISyntaxException; import java.util.List; +import java.io.File; + +import android.graphics.Movie; +import android.graphics.drawable.Drawable; +import android.os.SystemClock; +import android.util.Log; + +import androidx.annotation.NonNull; + +// Import for NavigationUtils +import me.edgan.redditslide.ForceTouch.util.NavigationUtils; public class TumblrView extends RecyclerView.Adapter { private final List users; private final Activity main; + private static final String TAG = "TumblrView"; public boolean paddingBottom; public int height; public String subreddit; + private static final int VIEW_TYPE_IMAGE = 1; + private static final int VIEW_TYPE_SPACER = 6; + private static final int VIEW_TYPE_GIF = 2; + public TumblrView( final Activity context, final List users, int height, String subreddit) { @@ -83,7 +102,8 @@ public void onItemClick( .scrollToPositionWithOffset( position + 1, context.findViewById( - R.id + R + .id .toolbar) .getHeight()); } else { @@ -112,11 +132,16 @@ public void onItemClick( @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { - if (viewType == 1) { + if (viewType == VIEW_TYPE_IMAGE) { View v = LayoutInflater.from(parent.getContext()) .inflate(R.layout.album_image, parent, false); return new AlbumViewHolder(v); + } else if (viewType == VIEW_TYPE_GIF) { + View v = + LayoutInflater.from(parent.getContext()) + .inflate(R.layout.list_item_tumblr_gif, parent, false); + return new GifViewHolder(v); } else { View v = LayoutInflater.from(parent.getContext()) @@ -132,131 +157,253 @@ public double getHeightFromAspectRatio(int imageHeight, int imageWidth, int view @Override public int getItemViewType(int position) { - int SPACER = 6; if (!paddingBottom && position == 0) { - return SPACER; + return VIEW_TYPE_SPACER; } else if (paddingBottom && position == getItemCount() - 1) { - return SPACER; + return VIEW_TYPE_SPACER; } else { - return 1; + int dataPosition = paddingBottom ? position : position -1; + if (dataPosition < 0 || dataPosition >= users.size()) { + return VIEW_TYPE_SPACER; + } + Photo photo = users.get(dataPosition); + try { + if (ContentType.isGif(new URI(photo.getOriginalSize().getUrl()))) { + return VIEW_TYPE_GIF; + } else { + return VIEW_TYPE_IMAGE; + } + } catch (URISyntaxException e) { + e.printStackTrace(); + return VIEW_TYPE_IMAGE; + } } } @Override - public void onBindViewHolder(RecyclerView.ViewHolder holder2, int i) { - if (holder2 instanceof AlbumViewHolder) { + public void onBindViewHolder(RecyclerView.ViewHolder holder, int i) { + if (holder instanceof AlbumViewHolder) { final int position = paddingBottom ? i : i - 1; + if (position < 0 || position >= users.size()) return; - AlbumViewHolder holder = (AlbumViewHolder) holder2; - + AlbumViewHolder albumHolder = (AlbumViewHolder) holder; final Photo user = users.get(position); + ((Reddit) main.getApplicationContext()) .getImageLoader() .displayImage( user.getOriginalSize().getUrl(), - holder.image, + albumHolder.image, ImageGridAdapter.options); - holder.body.setVisibility(View.VISIBLE); - holder.text.setVisibility(View.VISIBLE); - View imageView = holder.image; - if (imageView.getWidth() == 0) { - holder.image.setLayoutParams( + albumHolder.body.setVisibility(View.VISIBLE); + albumHolder.text.setVisibility(View.VISIBLE); + View imageView = albumHolder.image; + + if (user.getOriginalSize().getWidth() > 0 && user.getOriginalSize().getHeight() > 0) { + if (imageView.getWidth() == 0) { + albumHolder.image.setLayoutParams( new LinearLayout.LayoutParams( RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.WRAP_CONTENT)); + } else { + albumHolder.image.setLayoutParams( + new LinearLayout.LayoutParams( + RelativeLayout.LayoutParams.MATCH_PARENT, + (int) getHeightFromAspectRatio( + user.getOriginalSize().getHeight(), + user.getOriginalSize().getWidth(), + imageView.getWidth()))); + } } else { - holder.image.setLayoutParams( + albumHolder.image.setLayoutParams( new LinearLayout.LayoutParams( RelativeLayout.LayoutParams.MATCH_PARENT, - (int) - getHeightFromAspectRatio( - user.getOriginalSize().getHeight(), - user.getOriginalSize().getWidth(), - imageView.getWidth()))); + RelativeLayout.LayoutParams.WRAP_CONTENT)); } + { int type = - new FontPreferences(holder.body.getContext()) + new FontPreferences(albumHolder.body.getContext()) .getFontTypeComment() .getTypeface(); Typeface typeface; if (type >= 0) { - typeface = RobotoTypefaces.obtainTypeface(holder.body.getContext(), type); + typeface = RobotoTypefaces.obtainTypeface(albumHolder.body.getContext(), type); } else { typeface = Typeface.DEFAULT; } - holder.body.setTypeface(typeface); + albumHolder.body.setTypeface(typeface); } { int type = - new FontPreferences(holder.body.getContext()) + new FontPreferences(albumHolder.text.getContext()) .getFontTypeTitle() .getTypeface(); Typeface typeface; if (type >= 0) { - typeface = RobotoTypefaces.obtainTypeface(holder.body.getContext(), type); + typeface = RobotoTypefaces.obtainTypeface(albumHolder.text.getContext(), type); } else { typeface = Typeface.DEFAULT; } - holder.text.setTypeface(typeface); - } - { - holder.text.setVisibility(View.GONE); + albumHolder.text.setTypeface(typeface); } - { - if (user.getCaption() != null) { - List text = SubmissionParser.getBlocks(user.getCaption()); - LinkUtil.setTextWithLinks(text.get(0), holder.body); - if (holder.body.getText().toString().isEmpty()) { - holder.body.setVisibility(View.GONE); - } + + if (user.getCaption() != null) { + List textBlocks = SubmissionParser.getBlocks(user.getCaption()); + String captionText = textBlocks.get(0).trim(); + LinkUtil.setTextWithLinks(captionText, albumHolder.body); + albumHolder.text.setVisibility(View.GONE); + + if (albumHolder.body.getText().toString().isEmpty()) { + albumHolder.body.setVisibility(View.GONE); } else { - holder.body.setVisibility(View.GONE); + albumHolder.body.setVisibility(View.VISIBLE); } + } else { + albumHolder.text.setVisibility(View.GONE); + albumHolder.body.setVisibility(View.GONE); } - View.OnClickListener onGifImageClickListener = - new View.OnClickListener() { + albumHolder.itemView.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + if (SettingValues.image) { + Intent myIntent = new Intent(main, MediaView.class); + myIntent.putExtra(MediaView.SUBREDDIT, subreddit); + myIntent.putExtra(MediaView.EXTRA_URL, user.getOriginalSize().getUrl()); + if (((Tumblr)main).submissionTitle != null) { + myIntent.putExtra(MediaView.EXTRA_SUBMISSION_TITLE, ((Tumblr)main).submissionTitle); + } + main.startActivity(myIntent); + } else { + LinkUtil.openExternally(user.getOriginalSize().getUrl()); + } + } + }); + + } else if (holder instanceof GifViewHolder) { + final int position = paddingBottom ? i : i - 1; + if (position < 0 || position >= users.size()) return; + + final GifViewHolder gifHolder = (GifViewHolder) holder; + final Photo user = users.get(position); + final String gifUrl = user.getOriginalSize().getUrl(); + + // Tag the itemView with the URL to check in callbacks + gifHolder.itemView.setTag(gifUrl); + + gifHolder.gifLoader.setVisibility(View.VISIBLE); + gifHolder.gifDisplay.setVisibility(View.GONE); + gifHolder.gifDisplay.setImageDrawable(null); // Clear previous drawable + if (gifHolder.gifCaption != null) gifHolder.gifCaption.setVisibility(View.GONE); + + GifUtils.downloadGif(gifUrl, new GifUtils.GifDownloadCallback() { + @Override + public void onGifDownloaded(File gifFile) { + // Check if the ViewHolder is still bound to the same URL + if (!gifUrl.equals(gifHolder.itemView.getTag()) || main == null || main.isFinishing()) { + return; + } + main.runOnUiThread(new Runnable() { @Override - public void onClick(View view) { - if (SettingValues.image) { - Intent myIntent = new Intent(main, MediaView.class); - myIntent.putExtra(MediaView.SUBREDDIT, subreddit); - myIntent.putExtra( - MediaView.EXTRA_URL, user.getOriginalSize().getUrl()); - main.startActivity(myIntent); + public void run() { + // Double check tag inside UI thread as well, just in case + if (!gifUrl.equals(gifHolder.itemView.getTag())) { + return; + } + gifHolder.gifLoader.setVisibility(View.GONE); + Movie movie = Movie.decodeFile(gifFile.getAbsolutePath()); + if (movie != null) { + GifDrawable gifDrawable = new GifDrawable(movie, new Drawable.Callback() { + @Override + public void invalidateDrawable(@NonNull Drawable who) { + gifHolder.gifDisplay.invalidate(); + } + + @Override + public void scheduleDrawable(@NonNull Drawable who, @NonNull Runnable what, long when) { + gifHolder.gifDisplay.postDelayed(what, when - SystemClock.uptimeMillis()); + } + + @Override + public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what) { + gifHolder.gifDisplay.removeCallbacks(what); + } + }); + gifHolder.gifDisplay.setImageDrawable(gifDrawable); + gifHolder.gifDisplay.setVisibility(View.VISIBLE); + gifDrawable.start(); + + if (gifHolder.gifCaption != null && user.getCaption() != null) { + List textBlocks = SubmissionParser.getBlocks(user.getCaption()); + String captionText = textBlocks.get(0).trim(); + if (!captionText.isEmpty()){ + LinkUtil.setTextWithLinks(captionText, gifHolder.gifCaption); + gifHolder.gifCaption.setVisibility(View.VISIBLE); + } + } + } else { - LinkUtil.openExternally(user.getOriginalSize().getUrl()); + Log.e(TAG, "Failed to decode GIF: " + gifUrl); + if (gifHolder.gifCaption != null) { + LinkUtil.setTextWithLinks("Failed to load GIF.", gifHolder.gifCaption); + gifHolder.gifCaption.setVisibility(View.VISIBLE); + } } } - }; + }); + } - try { - if (ContentType.isGif(new URI(user.getOriginalSize().getUrl()))) { - holder.body.setVisibility(View.VISIBLE); - holder.body.setSingleLine(false); - holder.body.setTextHtml( - holder.text.getText() - + main.getString(R.string.submission_tap_gif) - .toUpperCase()); // got rid of the \n thing, because it - // didnt parse and it was already a new - // line so... - holder.body.setOnClickListener(onGifImageClickListener); + @Override + public void onGifDownloadFailed(Exception e) { + // Check if the ViewHolder is still bound to the same URL + if (!gifUrl.equals(gifHolder.itemView.getTag()) || main == null || main.isFinishing()) { + return; + } + main.runOnUiThread(new Runnable() { + @Override + public void run() { + // Double check tag inside UI thread as well + if (!gifUrl.equals(gifHolder.itemView.getTag())) { + return; + } + gifHolder.gifLoader.setVisibility(View.GONE); + Log.e(TAG, "Failed to download GIF: " + gifUrl, e); + if (gifHolder.gifCaption != null) { + LinkUtil.setTextWithLinks("Failed to download GIF.", gifHolder.gifCaption); + gifHolder.gifCaption.setVisibility(View.VISIBLE); + } + } + }); } - } catch (URISyntaxException e) { - e.printStackTrace(); + }, main, ((Tumblr)main).submissionTitle); + + } else if (holder instanceof SpacerViewHolder) { + View v = ((SpacerViewHolder) holder).itemView; + if (i == 0) { + v.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, height)); + } else { + v.setLayoutParams( + new RecyclerView.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + NavigationUtils.getNavBarHeight(main))); } + } + } - holder.itemView.setOnClickListener(onGifImageClickListener); - } else if (holder2 instanceof SpacerViewHolder) { - holder2.itemView - .findViewById(R.id.height) - .setLayoutParams( - new LinearLayout.LayoutParams( - holder2.itemView.getWidth(), - paddingBottom - ? height - : main.findViewById(R.id.toolbar).getHeight())); + @Override + public void onViewRecycled(@NonNull RecyclerView.ViewHolder holder) { + super.onViewRecycled(holder); + if (holder instanceof GifViewHolder) { + GifViewHolder gifHolder = (GifViewHolder) holder; + Drawable drawable = gifHolder.gifDisplay.getDrawable(); + if (drawable instanceof GifDrawable) { + GifDrawable gifDrawable = (GifDrawable) drawable; + gifDrawable.setCallback(null); + gifDrawable.stop(); + } + gifHolder.gifDisplay.setImageDrawable(null); + gifHolder.itemView.setTag(null); } } @@ -278,9 +425,22 @@ public static class AlbumViewHolder extends RecyclerView.ViewHolder { public AlbumViewHolder(View itemView) { super(itemView); - text = itemView.findViewById(R.id.imagetitle); - body = itemView.findViewById(R.id.imageCaption); + text = itemView.findViewById(R.id.text); + body = itemView.findViewById(R.id.body); image = itemView.findViewById(R.id.image); } } + + public static class GifViewHolder extends RecyclerView.ViewHolder { + final ImageView gifDisplay; + final ProgressBar gifLoader; + final SpoilerRobotoTextView gifCaption; + + public GifViewHolder(View itemView) { + super(itemView); + gifDisplay = itemView.findViewById(R.id.gif_display); + gifLoader = itemView.findViewById(R.id.gif_loader); + gifCaption = itemView.findViewById(R.id.gif_caption); + } + } } diff --git a/app/src/main/java/me/edgan/redditslide/util/GifUtils.java b/app/src/main/java/me/edgan/redditslide/util/GifUtils.java index ba41f0f23..468479013 100644 --- a/app/src/main/java/me/edgan/redditslide/util/GifUtils.java +++ b/app/src/main/java/me/edgan/redditslide/util/GifUtils.java @@ -167,16 +167,39 @@ protected File doInBackground(Void... voids) { throw new Exception("Failed to download GIF: " + response); } - // Create unique filename based on URL - String fileName; - Log.d("GifUtils", "DownloadGifTask - submissionTitle: " + (submissionTitle != null ? "'" + submissionTitle + "'" : "null")); + // Create unique filename + String uniquePartFromUrl; + try { + Uri parsedUri = Uri.parse(url); + String lastSegment = parsedUri.getLastPathSegment(); + if (lastSegment != null && lastSegment.length() > 3 && lastSegment.contains(".")) { // Basic check for a file-like segment + uniquePartFromUrl = lastSegment; + } else { + // Fallback to hash of URL if no good segment found + uniquePartFromUrl = String.valueOf(url.hashCode()); + } + } catch (Exception e) { + // Fallback to hash of URL on any parsing error + uniquePartFromUrl = String.valueOf(url.hashCode()); + } + + String fileNamePrefix; if (submissionTitle != null && !submissionTitle.trim().isEmpty()) { - fileName = FileUtil.getValidFileName(submissionTitle, "", ".gif"); + // Use submission title as part of the prefix, sanitized + fileNamePrefix = FileUtil.getValidFileName(submissionTitle, "", "") + "_"; } else { - // If no title available, use a timestamp - fileName = System.currentTimeMillis() + ".gif"; + // If no title, use a timestamp for the prefix + fileNamePrefix = System.currentTimeMillis() + "_"; + } + + // Combine prefix, unique part from URL, and ensure .gif extension + String finalFileName = FileUtil.getValidFileName(fileNamePrefix + uniquePartFromUrl, "", ".gif"); + if (!finalFileName.toLowerCase().endsWith(".gif")) { // Ensure .gif extension if getValidFileName strips it + finalFileName += ".gif"; } - File gifFile = new File(context.getCacheDir(), fileName); + + File gifFile = new File(context.getCacheDir(), finalFileName); + Log.d(TAG, "Downloading GIF " + url + " to cache file: " + gifFile.getAbsolutePath()); InputStream inputStream = response.body().byteStream(); OutputStream outputStream = new FileOutputStream(gifFile); @@ -928,6 +951,7 @@ protected Uri doInBackground(String... sub) { return Uri.parse(url); case DIRECT: case TUMBLR: + // Reverted: Let MediaView handle direct .gif URLs from Tumblr return Uri.parse(url); case IMGUR: try { diff --git a/app/src/main/res/layout/activity_media.xml b/app/src/main/res/layout/activity_media.xml index 722bae3ae..a9c714f6c 100644 --- a/app/src/main/res/layout/activity_media.xml +++ b/app/src/main/res/layout/activity_media.xml @@ -33,6 +33,15 @@ + + + + + + + + + + + + + \ No newline at end of file From 705cf68223aa97c63b9b363fd327a3d8956692d5 Mon Sep 17 00:00:00 2001 From: Nathan Grennan Date: Tue, 20 May 2025 23:19:18 -0700 Subject: [PATCH 56/99] Fixed formatting --- .../me/edgan/redditslide/Activities/TumblrPager.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/app/src/main/java/me/edgan/redditslide/Activities/TumblrPager.java b/app/src/main/java/me/edgan/redditslide/Activities/TumblrPager.java index 9d7060a84..b8a2a0fb2 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/TumblrPager.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/TumblrPager.java @@ -462,12 +462,12 @@ public void unscheduleDrawable(@NonNull Drawable who, @NonNull Runnable what) { } else { // Optionally, show an error or fallback Log.e(TAG, "Failed to decode GIF: " + url); - if (videoView instanceof ExoVideoView) { - ((ExoVideoView) videoView).setVideoURI(Uri.parse(url), ExoVideoView.VideoType.STANDARD, null); // Fallback to ExoVideoView if Movie decoding fails - ((ExoVideoView) videoView).play(); - videoView.setVisibility(View.VISIBLE); - imageView.setVisibility(View.GONE); - } + if (videoView instanceof ExoVideoView) { + ((ExoVideoView) videoView).setVideoURI(Uri.parse(url), ExoVideoView.VideoType.STANDARD, null); // Fallback to ExoVideoView if Movie decoding fails + ((ExoVideoView) videoView).play(); + videoView.setVisibility(View.VISIBLE); + imageView.setVisibility(View.GONE); + } } } }); From 3a69bd9370b10f16d9fc5b038dc92c924b3d4e0b Mon Sep 17 00:00:00 2001 From: Nathan Grennan Date: Wed, 21 May 2025 01:17:24 -0700 Subject: [PATCH 57/99] Fixed an intermittent display issue with Selftext preview images --- .../SubmissionViews/HeaderImageLinkView.java | 126 ++++++++++++++++-- 1 file changed, 116 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/me/edgan/redditslide/SubmissionViews/HeaderImageLinkView.java b/app/src/main/java/me/edgan/redditslide/SubmissionViews/HeaderImageLinkView.java index d1ec85bb7..d36123d3e 100644 --- a/app/src/main/java/me/edgan/redditslide/SubmissionViews/HeaderImageLinkView.java +++ b/app/src/main/java/me/edgan/redditslide/SubmissionViews/HeaderImageLinkView.java @@ -27,7 +27,6 @@ import com.nostra13.universalimageloader.core.assist.FailReason; import com.nostra13.universalimageloader.core.display.FadeInBitmapDisplayer; import com.nostra13.universalimageloader.core.listener.ImageLoadingListener; -import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListener; import me.edgan.redditslide.ContentType; import me.edgan.redditslide.ForceTouch.PeekView; @@ -83,6 +82,9 @@ public class HeaderImageLinkView extends RelativeLayout { public ImageView backdrop; private boolean forceThumb; + private static final List PLACEHOLDER_URLS = + Arrays.asList("self", "default", "image", "nsfw", "spoiler", ""); + public HeaderImageLinkView(Context context) { super(context); init(); @@ -867,44 +869,148 @@ private String getLowQualityVariationUrl(Submission submission) { } private void displayThumbnail(String url, boolean full) { + if (url == null || PLACEHOLDER_URLS.contains(url)) { + LogUtil.v("Displaying thumbnail - invalid or placeholder URL: " + url + ", hiding view and thumbImage2."); + setVisibility(View.GONE); // Hides HeaderImageLinkView + if (thumbImage2 != null) { + thumbImage2.setVisibility(View.GONE); + } + if (full && wrapArea != null) { // if full view, wrapArea might have been made visible + wrapArea.setVisibility(View.GONE); + } + return; + } + if (!full) { thumbImage2.setVisibility(View.VISIBLE); } else { wrapArea.setVisibility(View.VISIBLE); } loadedUrl = url; + + ImageLoadingListener detailedListener = new ImageLoadingListener() { + @Override + public void onLoadingStarted(String imageUri, View view) {} + + @Override + public void onLoadingFailed(String imageUri, View view, FailReason failReason) { + LogUtil.e("UIL (Thumbnail): Loading FAILED for: " + imageUri + ", reason: " + failReason.getType() + ", cause: " + (failReason.getCause() != null ? failReason.getCause().getMessage() : "null")); + if (HeaderImageLinkView.this != null) { + HeaderImageLinkView.this.setVisibility(View.GONE); + } + } + + @Override + public void onLoadingComplete(String imageUri, View view, android.graphics.Bitmap loadedBitmap) { + if (loadedBitmap != null) { + if (loadedBitmap.getWidth() == 0 || loadedBitmap.getHeight() == 0) { + LogUtil.w("UIL (Thumbnail): Loaded bitmap has zero width or height for " + imageUri); + if (HeaderImageLinkView.this != null) { + HeaderImageLinkView.this.setVisibility(View.GONE); // Hide if bitmap is unusable + } + } + } else { + LogUtil.w("UIL (Thumbnail): Loading COMPLETE for " + imageUri + " but bitmap is NULL."); + if (HeaderImageLinkView.this != null) { + HeaderImageLinkView.this.setVisibility(View.GONE); // Hide if bitmap is null + } + } + } + + @Override + public void onLoadingCancelled(String imageUri, View view) { + LogUtil.w("UIL (Thumbnail): Loading CANCELLED for " + imageUri); + if (HeaderImageLinkView.this != null) { + HeaderImageLinkView.this.setVisibility(View.GONE); + } + } + }; + ((Reddit) getContext().getApplicationContext()) .getImageLoader() - .displayImage(url, thumbImage2); - setVisibility(View.GONE); + .displayImage(url, thumbImage2, detailedListener); // Use detailedListener + setVisibility(View.GONE); // This line was already here for thumbnails } private void displayFullImage(String url, boolean full) { + if (url == null || PLACEHOLDER_URLS.contains(url)) { + LogUtil.v("Displaying full image - invalid or placeholder URL for backdrop: " + url + ", hiding view."); + setVisibility(View.GONE); + if (thumbImage2 != null) { + thumbImage2.setVisibility(View.GONE); + } + if (wrapArea != null) { + wrapArea.setVisibility(View.GONE); + } + return; + } + loadedUrl = url; + ImageLoadingListener detailedListener = new ImageLoadingListener() { + @Override + public void onLoadingStarted(String imageUri, View view) {} - // Create ImageLoadingListener to handle errors - ImageLoadingListener errorListener = new SimpleImageLoadingListener() { @Override public void onLoadingFailed(String imageUri, View view, FailReason failReason) { - HeaderImageLinkView.this.setVisibility(View.GONE); + LogUtil.e("UIL (FullImage): Loading FAILED for: " + imageUri + ", reason: " + failReason.getType() + ", cause: " + (failReason.getCause() != null ? failReason.getCause().getMessage() : "null")); + if (HeaderImageLinkView.this != null) { + HeaderImageLinkView.this.setVisibility(View.GONE); + } + } + + @Override + public void onLoadingComplete(String imageUri, View view, android.graphics.Bitmap loadedBitmap) { + if (loadedBitmap != null) { + if (loadedBitmap.getWidth() == 0 || loadedBitmap.getHeight() == 0) { + LogUtil.w("UIL (FullImage): Loaded bitmap has zero width or height for " + imageUri); + // Don't hide HeaderImageLinkView here by default, let adjustViewBounds try. + // If it results in 0 height, it will be invisible anyway. + // Only hide if explicitly desired for 0-dim images. + } + // Ensure backdrop is visible if we successfully loaded an image and HeaderImageLinkView is meant to be visible. + if (view instanceof ImageView && HeaderImageLinkView.this.getVisibility() == View.VISIBLE) { + ((ImageView) view).setVisibility(View.VISIBLE); + } + } else { + LogUtil.w("UIL (FullImage): Loading COMPLETE for " + imageUri + " but bitmap is NULL."); + if (HeaderImageLinkView.this != null) { + HeaderImageLinkView.this.setVisibility(View.GONE); // Hide if bitmap is null + } + } + } + + @Override + public void onLoadingCancelled(String imageUri, View view) { + LogUtil.w("UIL (FullImage): Loading CANCELLED for " + imageUri); + if (HeaderImageLinkView.this != null) { + HeaderImageLinkView.this.setVisibility(View.GONE); + } } }; + // Ensure backdrop ImageView itself is visible before loading, if HeaderImageLinkView is meant to be visible. + // This is because UIL won't make it visible, and its default state is visible from XML, + // but good to be explicit if we are about to load an image into it. + if (backdrop != null && getVisibility() == View.VISIBLE) { + backdrop.setVisibility(View.VISIBLE); + } + if (!full) { ((Reddit) getContext().getApplicationContext()) .getImageLoader() - .displayImage(url, backdrop, bigOptions, errorListener); + .displayImage(url, backdrop, null, detailedListener); } else { ((Reddit) getContext().getApplicationContext()) .getImageLoader() - .displayImage(url, backdrop, bigOptions, errorListener); + .displayImage(url, backdrop, bigOptions, detailedListener); } setVisibility(View.VISIBLE); + if (!full) { - thumbImage2.setVisibility(View.GONE); + if (thumbImage2 != null) thumbImage2.setVisibility(View.GONE); } else { - wrapArea.setVisibility(View.GONE); + if (wrapArea != null) wrapArea.setVisibility(View.GONE); } } From 70410bba3dce3212ca62598f7abe57af90577973 Mon Sep 17 00:00:00 2001 From: Nathan Grennan Date: Wed, 21 May 2025 02:51:53 -0700 Subject: [PATCH 58/99] Fixed "./gradlew test" tests --- app/build.gradle | 14 ++++++++++++++ .../me/edgan/redditslide/OpenRedditLink.java | 8 ++++++++ .../redditslide/SpoilerRobotoTextView.java | 4 ++-- .../redditslide/test/ContentTypeTest.java | 18 ------------------ .../redditslide/test/OpenRedditLinkTest.java | 3 +++ .../redditslide/test/SpoilerTextTest.java | 16 +++++++++------- 6 files changed, 36 insertions(+), 27 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 47b43011a..662589293 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -123,6 +123,15 @@ android { testOptions { unitTests { includeAndroidResources true + returnDefaultValues = true + all { + jvmArgs = [ + '--add-opens=java.base/java.lang=ALL-UNNAMED', + '--add-opens=java.base/java.util=ALL-UNNAMED', + '--add-opens=java.base/java.lang.reflect=ALL-UNNAMED', + '--add-opens=java.base/java.util.regex=ALL-UNNAMED' + ] + } } } @@ -317,8 +326,13 @@ dependencies { /** Testing **/ testImplementation 'junit:junit:4.13.2' + testImplementation 'org.mockito:mockito-core:5.12.0' + testImplementation 'org.mockito:mockito-inline:5.2.0' + testImplementation 'org.robolectric:robolectric:4.13' + testImplementation 'androidx.test:core:1.6.1' testImplementation 'org.hamcrest:hamcrest:2.2' testImplementation 'org.powermock:powermock-module-junit4:2.0.9' + testImplementation 'org.powermock:powermock-api-mockito2:2.0.9' } spotless { diff --git a/app/src/main/java/me/edgan/redditslide/OpenRedditLink.java b/app/src/main/java/me/edgan/redditslide/OpenRedditLink.java index a5198e199..520f4969f 100644 --- a/app/src/main/java/me/edgan/redditslide/OpenRedditLink.java +++ b/app/src/main/java/me/edgan/redditslide/OpenRedditLink.java @@ -450,6 +450,14 @@ public static RedditLinkType getRedditLinkType(@NonNull Uri uri) { return RedditLinkType.SHORTENED; } + // Handle cases like reddit.com/comments/xxxx as shortened links + if (host.equals("reddit.com") && path.matches("(?i)/comments/[^/]+/?")) { + boolean isSubmissionOrComment = path.matches("(?i)/(?:r|u(?:ser)?)/[^/]+/comments/.*"); + if (!isSubmissionOrComment) { + return RedditLinkType.SHORTENED; + } + } + if (path.matches("(?i)/live/[^/]*")) { return RedditLinkType.LIVE; } else if (path.matches("(?i)/message/compose.*")) { diff --git a/app/src/main/java/me/edgan/redditslide/SpoilerRobotoTextView.java b/app/src/main/java/me/edgan/redditslide/SpoilerRobotoTextView.java index 3c266ddf5..e19a1bddd 100644 --- a/app/src/main/java/me/edgan/redditslide/SpoilerRobotoTextView.java +++ b/app/src/main/java/me/edgan/redditslide/SpoilerRobotoTextView.java @@ -88,9 +88,9 @@ public class SpoilerRobotoTextView extends RobotoTextView implements ClickableTe private List storedSpoilerSpans = new ArrayList<>(); private List storedSpoilerStarts = new ArrayList<>(); private List storedSpoilerEnds = new ArrayList<>(); - private static final Pattern htmlSpoilerPattern = + public static final Pattern htmlSpoilerPattern = Pattern.compile("([^<]*)"); - private static final Pattern nativeSpoilerPattern = + public static final Pattern nativeSpoilerPattern = Pattern.compile("([^<]*)"); private static class MatchPair { diff --git a/app/src/test/java/me/edgan/redditslide/test/ContentTypeTest.java b/app/src/test/java/me/edgan/redditslide/test/ContentTypeTest.java index fd8f3cebe..a247697bc 100644 --- a/app/src/test/java/me/edgan/redditslide/test/ContentTypeTest.java +++ b/app/src/test/java/me/edgan/redditslide/test/ContentTypeTest.java @@ -8,7 +8,6 @@ import me.edgan.redditslide.ContentType; import me.edgan.redditslide.ContentType.Type; -import me.edgan.redditslide.Reddit; import me.edgan.redditslide.SettingValues; import org.junit.BeforeClass; @@ -150,23 +149,6 @@ public void detectsWithoutScheme() { assertThat(ContentType.getContentType("//google.com"), is(not(Type.NONE))); } - @Test - public void detectsVideo() { - Reddit.videoPlugin = true; - assertThat( - ContentType.getContentType("https://www.youtube.com/watch?v=lX_pF03vCSU"), - is(Type.VIDEO)); - assertThat(ContentType.getContentType("https://youtu.be/lX_pF03vCSU"), is(Type.VIDEO)); - - assertThat(ContentType.getContentType("https://www.gifyoutube.com/"), is(not(Type.VIDEO))); - - Reddit.videoPlugin = false; - assertThat( - ContentType.getContentType("https://www.youtube.com/watch?v=lX_pF03vCSU"), - is(not(Type.VIDEO))); - assertThat(ContentType.getContentType("https://youtu.be/lX_pF03vCSU"), is(not(Type.VIDEO))); - } - @Test public void detectsStreamable() { assertThat(ContentType.getContentType("https://streamable.com/l41f"), is(Type.STREAMABLE)); diff --git a/app/src/test/java/me/edgan/redditslide/test/OpenRedditLinkTest.java b/app/src/test/java/me/edgan/redditslide/test/OpenRedditLinkTest.java index 9169cf4f0..698fb68f2 100644 --- a/app/src/test/java/me/edgan/redditslide/test/OpenRedditLinkTest.java +++ b/app/src/test/java/me/edgan/redditslide/test/OpenRedditLinkTest.java @@ -10,7 +10,10 @@ import me.edgan.redditslide.OpenRedditLink.RedditLinkType; import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +@RunWith(RobolectricTestRunner.class) public class OpenRedditLinkTest { // Less characters diff --git a/app/src/test/java/me/edgan/redditslide/test/SpoilerTextTest.java b/app/src/test/java/me/edgan/redditslide/test/SpoilerTextTest.java index 6b4e51623..db2e33fe6 100644 --- a/app/src/test/java/me/edgan/redditslide/test/SpoilerTextTest.java +++ b/app/src/test/java/me/edgan/redditslide/test/SpoilerTextTest.java @@ -4,23 +4,25 @@ import me.edgan.redditslide.SpoilerRobotoTextView; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; -import org.powermock.reflect.Whitebox; import java.util.ArrayList; import java.util.List; import java.util.regex.Pattern; @RunWith(PowerMockRunner.class) -@PrepareForTest(SpoilerRobotoTextView.class) public class SpoilerTextTest { - private final Pattern htmlSpoilerPattern = - Whitebox.getInternalState(SpoilerRobotoTextView.class, "htmlSpoilerPattern"); - private final Pattern nativeSpoilerPattern = - Whitebox.getInternalState(SpoilerRobotoTextView.class, "nativeSpoilerPattern"); + private Pattern htmlSpoilerPattern; + private Pattern nativeSpoilerPattern; + + @Before + public void setUp() { + htmlSpoilerPattern = SpoilerRobotoTextView.htmlSpoilerPattern; + nativeSpoilerPattern = SpoilerRobotoTextView.nativeSpoilerPattern; + } private final List htmlSpoilerTests = new ArrayList() { From 2478737d45baa4756eb92c40b7ec3a4be19d8011 Mon Sep 17 00:00:00 2001 From: PrimaryVision <155949587+PrimaryVision@users.noreply.github.com> Date: Wed, 21 May 2025 13:40:20 +0000 Subject: [PATCH 59/99] Fix Crash: NullPointerException returning from Non-Special Tabs --- .../me/edgan/redditslide/Activities/DrawerController.java | 6 ++++++ .../java/me/edgan/redditslide/Activities/MainActivity.java | 4 +++- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/me/edgan/redditslide/Activities/DrawerController.java b/app/src/main/java/me/edgan/redditslide/Activities/DrawerController.java index 19d469afe..321b76c60 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/DrawerController.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/DrawerController.java @@ -997,6 +997,12 @@ public void afterTextChanged(Editable editable) { } } + public void clearDrawerSearch() { + if (drawerSearch != null) { + drawerSearch.setText(""); + } + } + private void collapse(final LinearLayout v) { int finalHeight = v.getHeight(); ValueAnimator mAnimator = AnimatorUtil.slideAnimator(finalHeight, 0, v); diff --git a/app/src/main/java/me/edgan/redditslide/Activities/MainActivity.java b/app/src/main/java/me/edgan/redditslide/Activities/MainActivity.java index 35f467c2c..47a254946 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/MainActivity.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/MainActivity.java @@ -207,7 +207,9 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) { || SettingValues.subredditSearchMethod == Constants.SUBREDDIT_SEARCH_METHOD_BOTH) { drawerLayout.closeDrawers(); - drawerSearch.setText(""); + if (drawerController != null) { + drawerController.clearDrawerSearch(); + } } // clear the text from the toolbar search field From cf105bc8893a70c1e3f4ed64d9acbd278971d288 Mon Sep 17 00:00:00 2001 From: Nathan Grennan Date: Wed, 21 May 2025 17:42:44 -0700 Subject: [PATCH 60/99] Re-applying changes that were made to MainActivity.java post the MainActivity-classes branch - * Fixed Tapping on "Moderation" does nothing. And I can't hide it either from drawer items #111 - Added Hide subreddit tabs setting - Added more null checks to prevent crashes in MainActivity.java --- .../Activities/AsyncNotificationBadge.java | 41 +++++- .../Activities/DrawerController.java | 8 +- .../redditslide/Activities/MainActivity.java | 22 ++-- .../Activities/MainPagerAdapter.java | 120 +++++++++++++++--- .../Activities/MainPagerAdapterComment.java | 113 +++++++++++++++-- .../Adapters/SideArrayAdapter.java | 4 +- 6 files changed, 257 insertions(+), 51 deletions(-) diff --git a/app/src/main/java/me/edgan/redditslide/Activities/AsyncNotificationBadge.java b/app/src/main/java/me/edgan/redditslide/Activities/AsyncNotificationBadge.java index cb0a4cd7e..9c07e86b4 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/AsyncNotificationBadge.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/AsyncNotificationBadge.java @@ -33,6 +33,7 @@ public class AsyncNotificationBadge extends AsyncTask { private MainActivity activity; int count; boolean restart; + boolean isCurrentUserMod = false; // Track if current user is mod public AsyncNotificationBadge(MainActivity activity) { this.activity = activity; @@ -51,12 +52,19 @@ protected Void doInBackground(Void... params) { restart = true; return null; } + // Update current user's mod status Authentication.mod = me.isMod(); - Authentication.authentication - .edit() - .putBoolean(Reddit.SHARED_PREF_IS_MOD, Authentication.mod) - .apply(); + isCurrentUserMod = Authentication.mod; + + Authentication.authentication.edit().putBoolean(Reddit.SHARED_PREF_IS_MOD, Authentication.mod).apply(); + + // If this account is a moderator, load the moderated subreddits + if (Authentication.mod) { + UserSubscriptions.modOf = UserSubscriptions.getModeratedSubs(); + } else { + UserSubscriptions.modOf = null; + } if (Reddit.notificationTime != -1) { Reddit.notifications = new NotificationJobScheduler(activity); @@ -86,6 +94,19 @@ protected Void doInBackground(Void... params) { } } else { me = Authentication.reddit.me(); + + // Update current user's mod status + Authentication.mod = me.isMod(); + isCurrentUserMod = Authentication.mod; + + // If this account is a moderator, load the moderated subreddits + if (Authentication.mod) { + if (UserSubscriptions.modOf == null || UserSubscriptions.modOf.isEmpty()) { + UserSubscriptions.modOf = UserSubscriptions.getModeratedSubs(); + } + } else { + UserSubscriptions.modOf = null; + } } count = me.getInboxCount(); // Force reload of the LoggedInAccount object UserSubscriptions.doFriendsOfMain(activity); @@ -104,14 +125,19 @@ protected void onPostExecute(Void aVoid) { activity.restartTheme(); return; } + // Ensure headerMain is not null before accessing its children if (activity.headerMain == null) { Log.e(LogUtil.getTag(), "headerMain is null in AsyncNotificationBadge.onPostExecute"); return; // Cannot proceed without headerMain } - if (Authentication.mod && Authentication.didOnline) { - RelativeLayout mod = activity.headerMain.findViewById(R.id.mod); + // Always hide the mod button first + RelativeLayout mod = activity.headerMain.findViewById(R.id.mod); + mod.setVisibility(View.GONE); + + // Only show mod button if user is a mod and has moderated subreddits + if (isCurrentUserMod && UserSubscriptions.modOf != null && !UserSubscriptions.modOf.isEmpty() && Authentication.didOnline) { if (mod != null) { mod.setVisibility(View.VISIBLE); @@ -129,6 +155,7 @@ public void onSingleClick(View view) { Log.e(LogUtil.getTag(), "R.id.mod not found in headerMain"); } } + if (count != -1) { int oldCount = Reddit.appRestart.getInt("inbox", 0); if (count > oldCount) { @@ -183,4 +210,4 @@ public void onSingleClick(View v) { Log.e(LogUtil.getTag(), "R.id.count (View) not found in headerMain"); } } -} \ No newline at end of file +} diff --git a/app/src/main/java/me/edgan/redditslide/Activities/DrawerController.java b/app/src/main/java/me/edgan/redditslide/Activities/DrawerController.java index 19d469afe..b305c42ac 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/DrawerController.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/DrawerController.java @@ -204,7 +204,8 @@ public void onSingleClick(View view) { * If the user is a known mod, show the "Moderation" drawer item quickly to stop the UI * from jumping */ - if (UserSubscriptions.modOf != null && !UserSubscriptions.modOf.isEmpty() && Authentication.mod) { + header.findViewById(R.id.mod).setVisibility(View.GONE); + if (Authentication.mod && UserSubscriptions.modOf != null && !UserSubscriptions.modOf.isEmpty()) { header.findViewById(R.id.mod).setVisibility(View.VISIBLE); } @@ -362,6 +363,9 @@ public void onClick(View v) { } Authentication.name = accName; + // Reset moderator status for new account + Authentication.mod = false; + UserSubscriptions.modOf = null; UserSubscriptions.switchAccounts(); Reddit.forceRestart(mainActivity, true); } @@ -1100,4 +1104,4 @@ private void showUsernameDialog(boolean isMultireddit) { @Override public void afterTextChanged(Editable s) {} }); } -} \ No newline at end of file +} diff --git a/app/src/main/java/me/edgan/redditslide/Activities/MainActivity.java b/app/src/main/java/me/edgan/redditslide/Activities/MainActivity.java index 35f467c2c..5e92ed899 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/MainActivity.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/MainActivity.java @@ -190,13 +190,19 @@ public class MainActivity extends BaseActivity protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == SETTINGS_RESULT) { int current = pager.getCurrentItem(); + if (commentPager && current == currentComment) { current = current - 1; } - if (current < 0) current = 0; + + if (current < 0) { + current = 0; + } + adapter = new MainPagerAdapter(this, getSupportFragmentManager()); pager.setAdapter(adapter); pager.setCurrentItem(current); + if (mTabLayout != null) { mTabLayout.setupWithViewPager(pager); LayoutUtils.scrollToTabAfterLayout(mTabLayout, current); @@ -211,11 +217,14 @@ protected void onActivityResult(int requestCode, int resultCode, Intent data) { } // clear the text from the toolbar search field - if (findViewById(R.id.toolbar_search) != null) { - ((AutoCompleteTextView) findViewById(R.id.toolbar_search)).setText(""); + AutoCompleteTextView toolbarSearchField = (AutoCompleteTextView) findViewById(R.id.toolbar_search); + + if (toolbarSearchField != null) { + toolbarSearchField.setText(""); } View view = MainActivity.this.getCurrentFocus(); + if (view != null) { KeyboardUtil.hideKeyboard(this, view.getWindowToken(), 0); } @@ -486,7 +495,6 @@ public boolean onOptionsItemSelected(MenuItem item) { && !subreddit.equals("frontpage") && !subreddit.contains(".") && !subreddit.contains("+") - && !subreddit.contains(".") && !subreddit.contains("/m/")) { drawerLayout.openDrawer(GravityCompat.END); } else { @@ -738,9 +746,7 @@ public void afterTextChanged(Editable s) {} } @Override - public void onRequestPermissionsResult(int requestCode, - @NonNull String[] permissions, - @NonNull int[] grantResults) { + public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode == NOTIFICATION_PERMISSION_REQUEST_CODE) { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { @@ -775,7 +781,7 @@ protected void onCreate(final Bundle savedInstanceState) { .setMessage( "NSFW content is now disabled by default. If you are over the age of" + " 18, to re-enable NSFW content, visit Settings > Content" - + " settings") + + " settings") .setPositiveButton(R.string.btn_ok, null) .setCancelable(false) .show(); diff --git a/app/src/main/java/me/edgan/redditslide/Activities/MainPagerAdapter.java b/app/src/main/java/me/edgan/redditslide/Activities/MainPagerAdapter.java index 52ff8a8bc..3468cdf9f 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/MainPagerAdapter.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/MainPagerAdapter.java @@ -22,20 +22,30 @@ import me.edgan.redditslide.Adapters.SubredditPosts; +import me.edgan.redditslide.Authentication; import me.edgan.redditslide.Constants; import me.edgan.redditslide.Fragments.SubmissionsView; import me.edgan.redditslide.R; import me.edgan.redditslide.Reddit; import me.edgan.redditslide.SettingValues; +import me.edgan.redditslide.UserSubscriptions; import me.edgan.redditslide.Visuals.ColorPreferences; import me.edgan.redditslide.Visuals.Palette; import me.edgan.redditslide.util.LogUtil; import me.edgan.redditslide.util.StringUtil; +import java.util.Locale; + public class MainPagerAdapter extends FragmentStatePagerAdapter { protected SubmissionsView mCurrentFragment; private MainActivity mainActivity; + // Helper method to check if a subreddit is special (frontpage, all) or a multi-reddit + protected boolean isSpecialOrMulti(String subreddit) { + String lowercase = subreddit.toLowerCase(Locale.ENGLISH); + return UserSubscriptions.specialSubreddits.contains(lowercase) || lowercase.contains("/m/"); + } + // Modified constructor to accept MainActivity public MainPagerAdapter(MainActivity mainActivity, FragmentManager fm) { super(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT); @@ -159,7 +169,18 @@ public int getCount() { if (mainActivity.usedArray == null) { return 1; } else { - return mainActivity.usedArray.size(); + if (SettingValues.hideSubredditTabs) { + // Count special subreddits like frontpage, all, etc. and multi-reddits + int count = 0; + for (String sub : mainActivity.usedArray) { + if (isSpecialOrMulti(sub)) { + count++; + } + } + return count > 0 ? count : 1; // Always show at least one tab + } else { + return mainActivity.usedArray.size(); + } } } @@ -168,17 +189,62 @@ public int getCount() { public Fragment getItem(int i) { SubmissionsView f = new SubmissionsView(); Bundle args = new Bundle(); - String name; - if (i < mainActivity.usedArray.size()) { - if (mainActivity.multiNameToSubsMap.containsKey(mainActivity.usedArray.get(i))) { - name = mainActivity.multiNameToSubsMap.get(mainActivity.usedArray.get(i)); - } else { - name = mainActivity.usedArray.get(i); + String name = ""; // Initialize with default empty string + + if (SettingValues.hideSubredditTabs) { + int specialIndex = 0; + boolean found = false; + + for (String sub : mainActivity.usedArray) { + if (isSpecialOrMulti(sub)) { + if (specialIndex == i) { + // Ensure full path for multi-reddits even when hidden + if (sub.startsWith("/m/")) { + if (mainActivity.multiNameToSubsMap.containsKey(sub)) { + name = mainActivity.multiNameToSubsMap.get(sub); + } else { + // Construct full path if map lookup fails + name = "api/user/" + Authentication.name + sub; // sub already starts with /m/ + } + } else { + name = sub; // Standard special subreddits (frontpage, all) + } + found = true; + break; + } + specialIndex++; + } + } + + // Fallback to the first subreddit if no special subreddit or multi-reddit was found + if (!found && !mainActivity.usedArray.isEmpty()) { + name = mainActivity.usedArray.get(0); + // Handle potential multi-reddit fallback case + if (name.startsWith("/m/")) { + if (mainActivity.multiNameToSubsMap.containsKey(name)) { + name = mainActivity.multiNameToSubsMap.get(name); + } else { + // Construct full path if map lookup fails + name = "api/user/" + Authentication.name + name; // name already starts with /m/ + } + } } - args.putString("id", name); } else { - args.putString("id", "frontpage"); + if (mainActivity.usedArray.size() > i) { + String potentialMulti = mainActivity.usedArray.get(i); + if (mainActivity.multiNameToSubsMap.containsKey(potentialMulti)) { + name = mainActivity.multiNameToSubsMap.get(potentialMulti); // Use the full path from the map + } else if (potentialMulti.startsWith("/m/")) { + // If map lookup fails BUT it looks like a multi-reddit, construct the path + name = "api/user/" + Authentication.name + potentialMulti; // potentialMulti starts with /m/ + } else { + // Regular subreddit or other special case + name = potentialMulti; + } + } } + + args.putString("id", name); f.setArguments(args); return f; @@ -226,11 +292,9 @@ public void doSetPrimary(Object object, int position) { return; } - if (object != null - && getCurrentFragment() != object - && position != mainActivity.toOpenComments - && object instanceof SubmissionsView) { + if (object != null && getCurrentFragment() != object && position != mainActivity.toOpenComments && object instanceof SubmissionsView) { mainActivity.shouldLoad = mainActivity.usedArray.get(position); + if (mainActivity.multiNameToSubsMap.containsKey(mainActivity.usedArray.get(position))) { mainActivity.shouldLoad = mainActivity.multiNameToSubsMap.get(mainActivity.usedArray.get(position)); } else { @@ -238,6 +302,7 @@ && getCurrentFragment() != object } mCurrentFragment = ((SubmissionsView) object); + if (mCurrentFragment.posts == null && mCurrentFragment.isAdded()) { mCurrentFragment.doAdapter(); } @@ -251,10 +316,29 @@ public Fragment getCurrentFragment() { @Override public CharSequence getPageTitle(int position) { - if (mainActivity.usedArray != null && position < mainActivity.usedArray.size()) { - return StringUtil.abbreviate(mainActivity.usedArray.get(position), 25); - } else { - return ""; + if (mainActivity.usedArray != null) { + if (SettingValues.hideSubredditTabs) { + // Find the position-th special subreddit or multi-reddit + int specialIndex = 0; + for (String sub : mainActivity.usedArray) { + if (isSpecialOrMulti(sub)) { + if (specialIndex == position) { + // Display only the name part for tabs, e.g., "/m/tech" or "frontpage" + return StringUtil.abbreviate(sub, 25); + } + specialIndex++; + } + } + // Fallback to the first subreddit if no special subreddit or multi-reddit was found at index position + if (!mainActivity.usedArray.isEmpty()) { + // Display only the name part for tabs + return StringUtil.abbreviate(mainActivity.usedArray.get(0), 25); + } + } else { + // Display only the name part for tabs + return StringUtil.abbreviate(mainActivity.usedArray.get(position), 25); + } } + return ""; } -} \ No newline at end of file +} diff --git a/app/src/main/java/me/edgan/redditslide/Activities/MainPagerAdapterComment.java b/app/src/main/java/me/edgan/redditslide/Activities/MainPagerAdapterComment.java index d79b44889..c4347a3ad 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/MainPagerAdapterComment.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/MainPagerAdapterComment.java @@ -10,9 +10,11 @@ import java.util.Locale; +import me.edgan.redditslide.Authentication; import me.edgan.redditslide.Adapters.SubredditPosts; import me.edgan.redditslide.Fragments.CommentPage; import me.edgan.redditslide.Fragments.SubmissionsView; +import me.edgan.redditslide.SettingValues; import me.edgan.redditslide.Visuals.Palette; import me.edgan.redditslide.util.StringUtil; @@ -99,7 +101,20 @@ public int getCount() { if (mainActivity.usedArray == null) { return 1; } else { - return size; + if (SettingValues.hideSubredditTabs) { + // Count special subreddits and multi-reddits + int count = 0; + for (String sub : mainActivity.usedArray) { + if (isSpecialOrMulti(sub)) { + count++; + } + } + + // Always include the comment page + return count + 1; + } else { + return size; + } } } @@ -109,23 +124,74 @@ public Fragment getItem(int i) { if (mainActivity.openingComments == null || i != mainActivity.toOpenComments) { SubmissionsView f = new SubmissionsView(); Bundle args = new Bundle(); - if (mainActivity.usedArray.size() > i) { - if (mainActivity.multiNameToSubsMap.containsKey(mainActivity.usedArray.get(i))) { - args.putString("id", mainActivity.multiNameToSubsMap.get(mainActivity.usedArray.get(i))); + String name = ""; // Initialize name + + if (SettingValues.hideSubredditTabs) { + // Find the i-th special subreddit or multi-reddit + int specialIndex = 0; + boolean found = false; + + for (String s : mainActivity.usedArray) { + if (isSpecialOrMulti(s)) { + if (specialIndex == i) { + // Ensure full path for multi-reddits even when hidden + if (s.startsWith("/m/")) { + if (mainActivity.multiNameToSubsMap.containsKey(s)) { + name = mainActivity.multiNameToSubsMap.get(s); + } else { + // Construct full path if map lookup fails + name = "api/user/" + Authentication.name + s; // s already starts with /m/ + } + } else { + name = s; // Standard special subreddits (frontpage, all) + } + found = true; + break; + } + specialIndex++; + } + } + + // Fallback to the first subreddit if no special subreddit or multi-reddit was found at index i + if (!found && !mainActivity.usedArray.isEmpty()) { + name = mainActivity.usedArray.get(0); + // Handle potential multi-reddit fallback case + if (name.startsWith("/m/")) { + if (mainActivity.multiNameToSubsMap.containsKey(name)) { + name = mainActivity.multiNameToSubsMap.get(name); + } else { + // Construct full path if map lookup fails + name = "api/user/" + Authentication.name + name; // name already starts with /m/ + } + } + } + + } else if (mainActivity.usedArray.size() > i) { + String potentialMulti = mainActivity.usedArray.get(i); + if (mainActivity.multiNameToSubsMap.containsKey(potentialMulti)) { + name = mainActivity.multiNameToSubsMap.get(potentialMulti); // Use the full path from the map + } else if (potentialMulti.startsWith("/m/")) { + // If map lookup fails BUT it looks like a multi-reddit, construct the path + name = "api/user/" + Authentication.name + potentialMulti; // potentialMulti starts with /m/ } else { - args.putString("id", mainActivity.usedArray.get(i)); + // Regular subreddit or other special case + name = potentialMulti; } } + + if (!name.isEmpty()) { // Ensure name is not empty before putting in args + args.putString("id", name); + } f.setArguments(args); return f; - } else { Fragment f = new CommentPage(); Bundle args = new Bundle(); - String name = mainActivity.openingComments.getFullName(); - args.putString("id", name.substring(3)); + String submissionFullName = mainActivity.openingComments.getFullName(); + args.putString("id", submissionFullName.substring(3)); args.putBoolean("archived", mainActivity.openingComments.isArchived()); - args.putBoolean("contest", mainActivity.openingComments.getDataNode().get("contest_mode").asBoolean()); + args.putBoolean( + "contest", mainActivity.openingComments.getDataNode().get("contest_mode").asBoolean()); args.putBoolean("locked", mainActivity.openingComments.isLocked()); args.putInt("page", mainActivity.currentComment); args.putString("subreddit", mainActivity.openingComments.getSubredditName()); @@ -172,9 +238,30 @@ public int getItemPosition(@NonNull Object object) { @Override public CharSequence getPageTitle(int position) { if (mainActivity.usedArray != null && position != mainActivity.toOpenComments) { - return StringUtil.abbreviate(mainActivity.usedArray.get(position), 25); - } else { - return ""; + if (SettingValues.hideSubredditTabs) { + // Find the position-th special subreddit or multi-reddit + int specialIndex = 0; + for (String sub : mainActivity.usedArray) { + if (isSpecialOrMulti(sub)) { + if (specialIndex == position) { + // Display only the name part for tabs + return StringUtil.abbreviate(sub, 25); + } + specialIndex++; + } + } + // Fallback to the first subreddit if no special subreddit or multi-reddit was found at index position + if (!mainActivity.usedArray.isEmpty()) { + // Display only the name part for tabs + return StringUtil.abbreviate(mainActivity.usedArray.get(0), 25); + } + } else { + // Display only the name part for tabs + return StringUtil.abbreviate(mainActivity.usedArray.get(position), 25); + } + } else if (position == mainActivity.toOpenComments) { + return "Comments"; } + return ""; } -} \ No newline at end of file +} diff --git a/app/src/main/java/me/edgan/redditslide/Adapters/SideArrayAdapter.java b/app/src/main/java/me/edgan/redditslide/Adapters/SideArrayAdapter.java index b060ff857..40243d04f 100644 --- a/app/src/main/java/me/edgan/redditslide/Adapters/SideArrayAdapter.java +++ b/app/src/main/java/me/edgan/redditslide/Adapters/SideArrayAdapter.java @@ -194,9 +194,7 @@ public void onGlobalLayout() { new View.OnClickListener() { @Override public void onClick(View view) { - boolean isSpecialOrMulti = UserSubscriptions.specialSubreddits.contains(subreddit.toLowerCase(Locale.ENGLISH)) - || subreddit.startsWith("/m/"); - + boolean isSpecialOrMulti = UserSubscriptions.specialSubreddits.contains(subreddit.toLowerCase(Locale.ENGLISH)) || subreddit.startsWith("/m/"); if (SettingValues.hideSubredditTabs) { // WHEN TABS ARE HIDDEN: if (isSpecialOrMulti) { From 918d13c45d176f9514c91193e2e3cf413368ebe2 Mon Sep 17 00:00:00 2001 From: Nathan Grennan Date: Thu, 22 May 2025 01:49:58 -0700 Subject: [PATCH 61/99] Added EXPIRES_VALUE as a constant in Constants.java --- .../main/java/me/edgan/redditslide/Authentication.java | 8 ++++---- app/src/main/java/me/edgan/redditslide/Constants.java | 3 +++ 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/me/edgan/redditslide/Authentication.java b/app/src/main/java/me/edgan/redditslide/Authentication.java index f29275f44..babd035fb 100644 --- a/app/src/main/java/me/edgan/redditslide/Authentication.java +++ b/app/src/main/java/me/edgan/redditslide/Authentication.java @@ -148,7 +148,7 @@ protected Void doInBackground(Void... params) { .edit() .putLong( "expires", - Calendar.getInstance().getTimeInMillis() + 3000000) + Calendar.getInstance().getTimeInMillis() + Constants.EXPIRES_VALUE) .commit(); } authentication @@ -182,7 +182,7 @@ protected Void doInBackground(Void... params) { .edit() .putLong( "expires", - Calendar.getInstance().getTimeInMillis() + 3000000) + Calendar.getInstance().getTimeInMillis() + Constants.EXPIRES_VALUE) .commit(); authentication .edit() @@ -294,7 +294,7 @@ public static void doVerify( .edit() .putLong( "expires", - Calendar.getInstance().getTimeInMillis() + 3000000) + Calendar.getInstance().getTimeInMillis() + Constants.EXPIRES_VALUE) .apply(); } } @@ -343,7 +343,7 @@ public static void doVerify( authData = reddit.getOAuthHelper().easyAuth(fcreds); authentication .edit() - .putLong("expires", Calendar.getInstance().getTimeInMillis() + 3000000) + .putLong("expires", Calendar.getInstance().getTimeInMillis() + Constants.EXPIRES_VALUE) .apply(); authentication .edit() diff --git a/app/src/main/java/me/edgan/redditslide/Constants.java b/app/src/main/java/me/edgan/redditslide/Constants.java index 840bf76b6..6e1de472e 100644 --- a/app/src/main/java/me/edgan/redditslide/Constants.java +++ b/app/src/main/java/me/edgan/redditslide/Constants.java @@ -32,6 +32,9 @@ public class Constants { public static final int PTR_OFFSET_BOTTOM = DisplayUtil.dpToPxVertical(18); + // 1000 * 60 * 50 = 50 minutes in milliseconds + public static final int EXPIRES_VALUE = 3000000; + /** * Drawer swipe edge (navdrawer). The higher the value, the more sensitive the navdrawer swipe * area becomes. This is a percentage of the screen width. From 684c71a38e2c82437c26435c0c2197a29a0800da Mon Sep 17 00:00:00 2001 From: PrimaryVision <155949587+PrimaryVision@users.noreply.github.com> Date: Thu, 22 May 2025 20:57:18 +0530 Subject: [PATCH 62/99] Refactor search clearing logic to use clearDrawerSearch method in multiple classes --- .../me/edgan/redditslide/Activities/DrawerController.java | 4 ++-- .../java/me/edgan/redditslide/Activities/MainActivity.java | 5 +++++ .../java/me/edgan/redditslide/Adapters/SideArrayAdapter.java | 4 ++-- 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/me/edgan/redditslide/Activities/DrawerController.java b/app/src/main/java/me/edgan/redditslide/Activities/DrawerController.java index 321b76c60..1167b0208 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/DrawerController.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/DrawerController.java @@ -904,7 +904,7 @@ public void setDrawerSubList() { new View.OnClickListener() { @Override public void onClick(View v) { - drawerSearch.setText(""); + clearDrawerSearch(); } }); @@ -960,7 +960,7 @@ public boolean onEditorAction(TextView arg0, int arg1, KeyEvent arg2) { } mainActivity.drawerLayout.closeDrawers(); - drawerSearch.setText(""); + clearDrawerSearch(); View view = mainActivity.getCurrentFocus(); if (view != null) { diff --git a/app/src/main/java/me/edgan/redditslide/Activities/MainActivity.java b/app/src/main/java/me/edgan/redditslide/Activities/MainActivity.java index 47a254946..ed2cf69f3 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/MainActivity.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/MainActivity.java @@ -186,6 +186,11 @@ public class MainActivity extends BaseActivity SidebarController sidebarController; SidebarActions sidebarActions; SubredditSortController subredditSortController; + + public DrawerController getDrawerController() { + return drawerController; + } + @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == SETTINGS_RESULT) { diff --git a/app/src/main/java/me/edgan/redditslide/Adapters/SideArrayAdapter.java b/app/src/main/java/me/edgan/redditslide/Adapters/SideArrayAdapter.java index b060ff857..65e1d14d9 100644 --- a/app/src/main/java/me/edgan/redditslide/Adapters/SideArrayAdapter.java +++ b/app/src/main/java/me/edgan/redditslide/Adapters/SideArrayAdapter.java @@ -205,7 +205,7 @@ public void onClick(View view) { int pos = mainActivity.usedArray.indexOf(subreddit); mainActivity.pager.setCurrentItem(pos); mainActivity.drawerLayout.closeDrawers(); - if (mainActivity.drawerSearch != null) mainActivity.drawerSearch.setText(""); + ((MainActivity) getContext()).drawerSearch.setText(""); } else if (subreddit.equalsIgnoreCase("random") || subreddit.equalsIgnoreCase("randnsfw") || subreddit.equalsIgnoreCase("myrandom")) { @@ -253,7 +253,7 @@ public void onClick(View view) { } mainActivity.pager.setCurrentItem(pos); mainActivity.drawerLayout.closeDrawers(); - if (mainActivity.drawerSearch != null) mainActivity.drawerSearch.setText(""); + ((MainActivity) getContext()).getDrawerController().clearDrawerSearch(); } else if (subreddit.equalsIgnoreCase("random") || subreddit.equalsIgnoreCase("randnsfw") || subreddit.equalsIgnoreCase("myrandom")) { From 5e0ef347ff40ffc8eb8828803dd514341d0c8df5 Mon Sep 17 00:00:00 2001 From: Nathan Grennan Date: Thu, 22 May 2025 13:56:16 -0700 Subject: [PATCH 63/99] Removing comment --- app/src/main/java/me/edgan/redditslide/util/GifUtils.java | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/java/me/edgan/redditslide/util/GifUtils.java b/app/src/main/java/me/edgan/redditslide/util/GifUtils.java index 468479013..3771bdecb 100644 --- a/app/src/main/java/me/edgan/redditslide/util/GifUtils.java +++ b/app/src/main/java/me/edgan/redditslide/util/GifUtils.java @@ -951,7 +951,6 @@ protected Uri doInBackground(String... sub) { return Uri.parse(url); case DIRECT: case TUMBLR: - // Reverted: Let MediaView handle direct .gif URLs from Tumblr return Uri.parse(url); case IMGUR: try { From 1177f218a21962a230891c3139fd4d5e18a7c115 Mon Sep 17 00:00:00 2001 From: Nathan Grennan Date: Fri, 23 May 2025 00:53:53 -0700 Subject: [PATCH 64/99] Fixed cascade effect for Settings when using oldSwipeMode --- .../edgan/redditslide/ui/settings/SettingsActivity.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsActivity.java b/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsActivity.java index be56a9e9f..f5f93fff9 100644 --- a/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsActivity.java +++ b/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsActivity.java @@ -42,6 +42,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import android.util.TypedValue; /** Created by ccrama on 3/5/2015. */ public class SettingsActivity extends BaseActivity implements RestartActivity { @@ -129,6 +130,13 @@ public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); applyColorTheme(); setContentView(R.layout.activity_settings); + + if (SettingValues.oldSwipeMode) { + TypedValue typedValue = new TypedValue(); + getTheme().resolveAttribute(R.attr.card_background, typedValue, true); + getWindow().getDecorView().setBackgroundColor(typedValue.data); + } + setupAppBar(R.id.toolbar, R.string.title_settings, true, true); if (getIntent() != null From 16d95fb6ef45edf986832ff73e3640d3c99a77d1 Mon Sep 17 00:00:00 2001 From: Nathan Grennan Date: Fri, 23 May 2025 01:01:22 -0700 Subject: [PATCH 65/99] Fixed cascade effect for Manage offline content when using oldSwipeMode --- .../redditslide/ui/settings/ManageOfflineContent.java | 9 +++++++++ .../edgan/redditslide/ui/settings/SettingsActivity.java | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/me/edgan/redditslide/ui/settings/ManageOfflineContent.java b/app/src/main/java/me/edgan/redditslide/ui/settings/ManageOfflineContent.java index d84139eaf..edd237a6d 100644 --- a/app/src/main/java/me/edgan/redditslide/ui/settings/ManageOfflineContent.java +++ b/app/src/main/java/me/edgan/redditslide/ui/settings/ManageOfflineContent.java @@ -1,10 +1,12 @@ package me.edgan.redditslide.ui.settings; import android.os.Bundle; +import android.util.TypedValue; import android.view.ViewGroup; import me.edgan.redditslide.Activities.BaseActivityAnim; import me.edgan.redditslide.R; +import me.edgan.redditslide.SettingValues; /** Created by l3d00m on 11/13/2015. */ public class ManageOfflineContent extends BaseActivityAnim { @@ -15,6 +17,13 @@ public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); applyColorTheme(); setContentView(R.layout.activity_manage_history); + + if (SettingValues.oldSwipeMode) { + TypedValue typedValue = new TypedValue(); + getTheme().resolveAttribute(R.attr.card_background, typedValue, true); + getWindow().getDecorView().setBackgroundColor(typedValue.data); + } + setupAppBar(R.id.toolbar, R.string.manage_offline_content, true, true); ((ViewGroup) findViewById(R.id.manage_history)) diff --git a/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsActivity.java b/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsActivity.java index f5f93fff9..f36c8766f 100644 --- a/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsActivity.java +++ b/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsActivity.java @@ -7,6 +7,7 @@ import android.content.res.Resources; import android.os.Bundle; import android.os.SystemClock; +import android.util.TypedValue; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.Menu; @@ -42,7 +43,6 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import android.util.TypedValue; /** Created by ccrama on 3/5/2015. */ public class SettingsActivity extends BaseActivity implements RestartActivity { From 2a0d700763c5606ce14c385d42afe4a8970ab032 Mon Sep 17 00:00:00 2001 From: Nathan Grennan Date: Fri, 23 May 2025 02:10:29 -0700 Subject: [PATCH 66/99] Fixed lots of cascade effect across many screens --- .../java/me/edgan/redditslide/Activities/Discover.java | 8 ++++++++ .../java/me/edgan/redditslide/Activities/Inbox.java | 8 ++++++++ .../me/edgan/redditslide/Activities/PostReadLater.java | 10 ++++++++++ .../java/me/edgan/redditslide/Activities/Profile.java | 8 ++++++++ .../edgan/redditslide/ui/settings/SettingsAbout.java | 9 +++++++++ .../redditslide/ui/settings/SettingsComments.java | 9 +++++++++ .../me/edgan/redditslide/ui/settings/SettingsData.java | 9 +++++++++ .../edgan/redditslide/ui/settings/SettingsFilter.java | 8 ++++++++ .../me/edgan/redditslide/ui/settings/SettingsFont.java | 9 +++++++++ .../edgan/redditslide/ui/settings/SettingsGeneral.java | 8 ++++++++ .../redditslide/ui/settings/SettingsHandling.java | 8 ++++++++ .../edgan/redditslide/ui/settings/SettingsHistory.java | 9 +++++++++ .../redditslide/ui/settings/SettingsModeration.java | 9 +++++++++ .../edgan/redditslide/ui/settings/SettingsReddit.java | 9 +++++++++ .../edgan/redditslide/ui/settings/SettingsSynccit.java | 8 ++++++++ .../edgan/redditslide/ui/settings/SettingsTheme.java | 9 +++++++++ .../ui/settings/dragSort/ReorderSubreddits.java | 8 ++++++++ .../edgan/redditslide/ui/settings/SettingsBackup.java | 8 ++++++++ .../edgan/redditslide/ui/settings/SettingsBackup.java | 9 +++++++++ 19 files changed, 163 insertions(+) diff --git a/app/src/main/java/me/edgan/redditslide/Activities/Discover.java b/app/src/main/java/me/edgan/redditslide/Activities/Discover.java index 60231eb57..2bd905739 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/Discover.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/Discover.java @@ -3,6 +3,7 @@ import android.content.Intent; import android.os.Bundle; import android.text.InputType; +import android.util.TypedValue; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; @@ -21,6 +22,7 @@ import me.edgan.redditslide.Fragments.SubredditListView; import me.edgan.redditslide.R; +import me.edgan.redditslide.SettingValues; import me.edgan.redditslide.Visuals.ColorPreferences; import me.edgan.redditslide.Visuals.Palette; @@ -93,6 +95,12 @@ public void onCreate(Bundle savedInstance) { applyColorTheme(""); setContentView(R.layout.activity_multireddits); + if (SettingValues.oldSwipeMode) { + TypedValue typedValue = new TypedValue(); + getTheme().resolveAttribute(R.attr.card_background, typedValue, true); + getWindow().getDecorView().setBackgroundColor(typedValue.data); + } + ((DrawerLayout) findViewById(R.id.drawer_layout)) .setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED); setupAppBar(R.id.toolbar, R.string.discover_title, true, false); diff --git a/app/src/main/java/me/edgan/redditslide/Activities/Inbox.java b/app/src/main/java/me/edgan/redditslide/Activities/Inbox.java index cd9334286..7ec927c2f 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/Inbox.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/Inbox.java @@ -3,6 +3,7 @@ import android.content.Intent; import android.os.AsyncTask; import android.os.Bundle; +import android.util.TypedValue; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; @@ -184,6 +185,13 @@ protected Void doInBackground(Void... params) { SettingValues.prefs.edit().putLong("lastInbox", System.currentTimeMillis()).apply(); applyColorTheme(""); setContentView(R.layout.activity_inbox); + + if (SettingValues.oldSwipeMode) { + TypedValue typedValue = new TypedValue(); + getTheme().resolveAttribute(R.attr.card_background, typedValue, true); + getWindow().getDecorView().setBackgroundColor(typedValue.data); + } + setupAppBar(R.id.toolbar, R.string.title_inbox, true, true); mToolbar.setPopupTheme(new ColorPreferences(this).getFontStyle().getBaseId()); diff --git a/app/src/main/java/me/edgan/redditslide/Activities/PostReadLater.java b/app/src/main/java/me/edgan/redditslide/Activities/PostReadLater.java index 36c9bf3df..b5bff0a9f 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/PostReadLater.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/PostReadLater.java @@ -1,6 +1,7 @@ package me.edgan.redditslide.Activities; import android.os.Bundle; +import android.util.TypedValue; import androidx.annotation.NonNull; import androidx.fragment.app.Fragment; @@ -8,8 +9,10 @@ import androidx.fragment.app.FragmentStatePagerAdapter; import androidx.viewpager.widget.ViewPager; +import me.edgan.redditslide.SettingValues; import me.edgan.redditslide.Fragments.ReadLaterView; import me.edgan.redditslide.R; +import me.edgan.redditslide.SettingValues; import me.edgan.redditslide.Visuals.ColorPreferences; /** Created by ccrama on 9/17/2015. */ @@ -23,6 +26,13 @@ public void onCreate(Bundle savedInstance) { applyColorTheme(); setContentView(R.layout.activity_read_later); + + if (SettingValues.oldSwipeMode) { + TypedValue typedValue = new TypedValue(); + getTheme().resolveAttribute(R.attr.card_background, typedValue, true); + getWindow().getDecorView().setBackgroundColor(typedValue.data); + } + setupAppBar(R.id.toolbar, this.getString(R.string.read_later), true, true); mToolbar.setPopupTheme(new ColorPreferences(this).getFontStyle().getBaseId()); diff --git a/app/src/main/java/me/edgan/redditslide/Activities/Profile.java b/app/src/main/java/me/edgan/redditslide/Activities/Profile.java index 090d97be6..2ba301d9f 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/Profile.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/Profile.java @@ -9,6 +9,7 @@ import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; +import android.util.TypedValue; import android.text.Spannable; import android.text.SpannableStringBuilder; import android.text.Spanned; @@ -118,6 +119,13 @@ public void onCreate(Bundle savedInstance) { applyColorTheme(); setContentView(R.layout.activity_profile); + + if (SettingValues.oldSwipeMode) { + TypedValue typedValue = new TypedValue(); + getTheme().resolveAttribute(R.attr.card_background, typedValue, true); + getWindow().getDecorView().setBackgroundColor(typedValue.data); + } + setupUserAppBar(R.id.toolbar, name, true, name); mToolbar.setPopupTheme(new ColorPreferences(this).getFontStyle().getBaseId()); diff --git a/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsAbout.java b/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsAbout.java index 277dbebbf..ddce0657e 100644 --- a/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsAbout.java +++ b/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsAbout.java @@ -4,6 +4,7 @@ import android.content.Intent; import android.content.SharedPreferences; import android.os.Bundle; +import android.util.TypedValue; import android.view.View; import android.widget.TextView; import android.widget.Toast; @@ -12,6 +13,7 @@ import me.edgan.redditslide.BuildConfig; import me.edgan.redditslide.OpenRedditLink; import me.edgan.redditslide.R; +import me.edgan.redditslide.SettingValues; import me.edgan.redditslide.util.ClipboardUtil; import me.edgan.redditslide.util.LinkUtil; @@ -22,6 +24,13 @@ public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); applyColorTheme(); setContentView(R.layout.activity_settings_about); + + if (SettingValues.oldSwipeMode) { + TypedValue typedValue = new TypedValue(); + getTheme().resolveAttribute(R.attr.card_background, typedValue, true); + getWindow().getDecorView().setBackgroundColor(typedValue.data); + } + setupAppBar(R.id.toolbar, R.string.settings_title_about, true, true); View privacy = findViewById(R.id.privacy); diff --git a/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsComments.java b/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsComments.java index ce948cf80..a3fc0658f 100644 --- a/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsComments.java +++ b/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsComments.java @@ -1,10 +1,12 @@ package me.edgan.redditslide.ui.settings; import android.os.Bundle; +import android.util.TypedValue; import android.view.ViewGroup; import me.edgan.redditslide.Activities.BaseActivityAnim; import me.edgan.redditslide.R; +import me.edgan.redditslide.SettingValues; public class SettingsComments extends BaseActivityAnim { @@ -14,6 +16,13 @@ public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); applyColorTheme(); setContentView(R.layout.activity_settings_comments); + + if (SettingValues.oldSwipeMode) { + TypedValue typedValue = new TypedValue(); + getTheme().resolveAttribute(R.attr.card_background, typedValue, true); + getWindow().getDecorView().setBackgroundColor(typedValue.data); + } + setupAppBar(R.id.toolbar, R.string.settings_title_comments, true, true); ((ViewGroup) findViewById(R.id.settings_comments)) diff --git a/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsData.java b/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsData.java index 3acbd5ba4..d31a48b21 100644 --- a/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsData.java +++ b/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsData.java @@ -1,10 +1,12 @@ package me.edgan.redditslide.ui.settings; import android.os.Bundle; +import android.util.TypedValue; import android.view.ViewGroup; import me.edgan.redditslide.Activities.BaseActivityAnim; import me.edgan.redditslide.R; +import me.edgan.redditslide.SettingValues; /** Created by ccrama on 3/5/2015. */ public class SettingsData extends BaseActivityAnim { @@ -15,6 +17,13 @@ public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); applyColorTheme(); setContentView(R.layout.activity_settings_datasaving); + + if (SettingValues.oldSwipeMode) { + TypedValue typedValue = new TypedValue(); + getTheme().resolveAttribute(R.attr.card_background, typedValue, true); + getWindow().getDecorView().setBackgroundColor(typedValue.data); + } + setupAppBar(R.id.toolbar, R.string.settings_data, true, true); ((ViewGroup) findViewById(R.id.settings_datasaving)) diff --git a/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsFilter.java b/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsFilter.java index ef22e97e8..1291f1828 100644 --- a/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsFilter.java +++ b/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsFilter.java @@ -7,6 +7,7 @@ import android.text.Spanned; import android.text.style.ForegroundColorSpan; import android.text.style.StyleSpan; +import android.util.TypedValue; import android.view.KeyEvent; import android.view.View; import android.view.inputmethod.EditorInfo; @@ -41,6 +42,13 @@ public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); applyColorTheme(); setContentView(R.layout.activity_settings_filters); + + if (SettingValues.oldSwipeMode) { + TypedValue typedValue = new TypedValue(); + getTheme().resolveAttribute(R.attr.card_background, typedValue, true); + getWindow().getDecorView().setBackgroundColor(typedValue.data); + } + setupAppBar(R.id.toolbar, R.string.settings_title_filter, true, true); // Initialize memory filters as empty at app start diff --git a/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsFont.java b/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsFont.java index 2b2b0a2b3..6d31e76c7 100644 --- a/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsFont.java +++ b/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsFont.java @@ -1,10 +1,12 @@ package me.edgan.redditslide.ui.settings; import android.os.Bundle; +import android.util.TypedValue; import android.view.ViewGroup; import me.edgan.redditslide.Activities.BaseActivityAnim; import me.edgan.redditslide.R; +import me.edgan.redditslide.SettingValues; /** Created by l3d00m on 11/13/2015. */ public class SettingsFont extends BaseActivityAnim { @@ -15,6 +17,13 @@ public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); applyColorTheme(); setContentView(R.layout.activity_settings_font); + + if (SettingValues.oldSwipeMode) { + TypedValue typedValue = new TypedValue(); + getTheme().resolveAttribute(R.attr.card_background, typedValue, true); + getWindow().getDecorView().setBackgroundColor(typedValue.data); + } + setupAppBar(R.id.toolbar, R.string.settings_title_font, true, true); ((ViewGroup) findViewById(R.id.settings_font)) diff --git a/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsGeneral.java b/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsGeneral.java index 6ce0f0640..da138bc26 100644 --- a/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsGeneral.java +++ b/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsGeneral.java @@ -5,6 +5,7 @@ import android.net.Uri; import android.os.Build; import android.os.Bundle; +import android.util.TypedValue; import android.view.ViewGroup; import android.widget.RelativeLayout; import android.widget.TextView; @@ -28,6 +29,13 @@ public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); applyColorTheme(); setContentView(R.layout.activity_settings_general); + + if (SettingValues.oldSwipeMode) { + TypedValue typedValue = new TypedValue(); + getTheme().resolveAttribute(R.attr.card_background, typedValue, true); + getWindow().getDecorView().setBackgroundColor(typedValue.data); + } + setupAppBar(R.id.toolbar, R.string.settings_title_general, true, true); ((ViewGroup) findViewById(R.id.settings_general)) diff --git a/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsHandling.java b/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsHandling.java index 9450f2031..2ed25b298 100644 --- a/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsHandling.java +++ b/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsHandling.java @@ -2,6 +2,7 @@ import android.content.SharedPreferences; import android.os.Bundle; +import android.util.TypedValue; import android.view.ViewGroup; import me.edgan.redditslide.Activities.BaseActivityAnim; @@ -17,6 +18,13 @@ public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); applyColorTheme(); setContentView(R.layout.activity_settings_handling); + + if (SettingValues.oldSwipeMode) { + TypedValue typedValue = new TypedValue(); + getTheme().resolveAttribute(R.attr.card_background, typedValue, true); + getWindow().getDecorView().setBackgroundColor(typedValue.data); + } + setupAppBar(R.id.toolbar, R.string.settings_link_handling, true, true); ((ViewGroup) findViewById(R.id.settings_handling)) diff --git a/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsHistory.java b/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsHistory.java index a2c2a1d11..c8f324379 100644 --- a/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsHistory.java +++ b/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsHistory.java @@ -1,10 +1,12 @@ package me.edgan.redditslide.ui.settings; import android.os.Bundle; +import android.util.TypedValue; import android.view.ViewGroup; import me.edgan.redditslide.Activities.BaseActivityAnim; import me.edgan.redditslide.R; +import me.edgan.redditslide.SettingValues; public class SettingsHistory extends BaseActivityAnim { @@ -15,6 +17,13 @@ protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); applyColorTheme(); setContentView(R.layout.activity_settings_history); + + if (SettingValues.oldSwipeMode) { + TypedValue typedValue = new TypedValue(); + getTheme().resolveAttribute(R.attr.card_background, typedValue, true); + getWindow().getDecorView().setBackgroundColor(typedValue.data); + } + setupAppBar(R.id.toolbar, R.string.settings_title_history, true, true); ((ViewGroup) findViewById(R.id.settings_history)) diff --git a/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsModeration.java b/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsModeration.java index 489f83aaf..70a2f0924 100644 --- a/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsModeration.java +++ b/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsModeration.java @@ -1,10 +1,12 @@ package me.edgan.redditslide.ui.settings; import android.os.Bundle; +import android.util.TypedValue; import android.view.ViewGroup; import me.edgan.redditslide.Activities.BaseActivityAnim; import me.edgan.redditslide.R; +import me.edgan.redditslide.SettingValues; public class SettingsModeration extends BaseActivityAnim { private SettingsModerationFragment fragment = new SettingsModerationFragment(this); @@ -14,6 +16,13 @@ protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); applyColorTheme(); setContentView(R.layout.activity_settings_moderation); + + if (SettingValues.oldSwipeMode) { + TypedValue typedValue = new TypedValue(); + getTheme().resolveAttribute(R.attr.card_background, typedValue, true); + getWindow().getDecorView().setBackgroundColor(typedValue.data); + } + setupAppBar(R.id.toolbar, R.string.settings_moderation, true, true); ((ViewGroup) findViewById(R.id.settings_moderation)) diff --git a/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsReddit.java b/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsReddit.java index 7e907cdee..6e0be2477 100644 --- a/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsReddit.java +++ b/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsReddit.java @@ -1,10 +1,12 @@ package me.edgan.redditslide.ui.settings; import android.os.Bundle; +import android.util.TypedValue; import android.view.ViewGroup; import me.edgan.redditslide.Activities.BaseActivityAnim; import me.edgan.redditslide.R; +import me.edgan.redditslide.SettingValues; /** Created by l3d00m on 11/13/2015. */ public class SettingsReddit extends BaseActivityAnim { @@ -16,6 +18,13 @@ public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); applyColorTheme(); setContentView(R.layout.activity_settings_reddit); + + if (SettingValues.oldSwipeMode) { + TypedValue typedValue = new TypedValue(); + getTheme().resolveAttribute(R.attr.card_background, typedValue, true); + getWindow().getDecorView().setBackgroundColor(typedValue.data); + } + setupAppBar(R.id.toolbar, R.string.settings_reddit_prefs, true, true); ((ViewGroup) findViewById(R.id.settings_reddit)) diff --git a/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsSynccit.java b/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsSynccit.java index 4aa2590f5..e3c37f383 100644 --- a/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsSynccit.java +++ b/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsSynccit.java @@ -3,6 +3,7 @@ import android.app.Dialog; import android.content.SharedPreferences; import android.os.Bundle; +import android.util.TypedValue; import android.view.View; import android.widget.EditText; @@ -29,6 +30,13 @@ public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); applyColorTheme(); setContentView(R.layout.activity_settings_synccit); + + if (SettingValues.oldSwipeMode) { + TypedValue typedValue = new TypedValue(); + getTheme().resolveAttribute(R.attr.card_background, typedValue, true); + getWindow().getDecorView().setBackgroundColor(typedValue.data); + } + setupAppBar(R.id.toolbar, R.string.settings_synccit, true, true); name = (EditText) findViewById(R.id.name); diff --git a/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsTheme.java b/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsTheme.java index c89d95f16..5c5d423fe 100644 --- a/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsTheme.java +++ b/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsTheme.java @@ -2,10 +2,12 @@ import android.content.Intent; import android.os.Bundle; +import android.util.TypedValue; import android.view.ViewGroup; import me.edgan.redditslide.Activities.BaseActivityAnim; import me.edgan.redditslide.R; +import me.edgan.redditslide.SettingValues; /** Created by ccrama on 3/5/2015. */ public class SettingsTheme extends BaseActivityAnim implements RestartActivity { @@ -16,6 +18,13 @@ public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); applyColorTheme(); setContentView(R.layout.activity_settings_theme); + + if (SettingValues.oldSwipeMode) { + TypedValue typedValue = new TypedValue(); + getTheme().resolveAttribute(R.attr.card_background, typedValue, true); + getWindow().getDecorView().setBackgroundColor(typedValue.data); + } + setupAppBar(R.id.toolbar, R.string.title_edit_theme, true, true); ((ViewGroup) findViewById(R.id.settings_theme)) diff --git a/app/src/main/java/me/edgan/redditslide/ui/settings/dragSort/ReorderSubreddits.java b/app/src/main/java/me/edgan/redditslide/ui/settings/dragSort/ReorderSubreddits.java index e1a28e9d2..2e9b50287 100644 --- a/app/src/main/java/me/edgan/redditslide/ui/settings/dragSort/ReorderSubreddits.java +++ b/app/src/main/java/me/edgan/redditslide/ui/settings/dragSort/ReorderSubreddits.java @@ -11,6 +11,7 @@ import android.text.Editable; import android.text.TextWatcher; import android.util.Log; +import android.util.TypedValue; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; @@ -223,6 +224,13 @@ protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); applyColorTheme(); setContentView(R.layout.activity_sort); + + if (SettingValues.oldSwipeMode) { + TypedValue typedValue = new TypedValue(); + getTheme().resolveAttribute(R.attr.card_background, typedValue, true); + getWindow().getDecorView().setBackgroundColor(typedValue.data); + } + setupAppBar(R.id.toolbar, R.string.settings_manage_subscriptions, false, true); mToolbar.setPopupTheme(new ColorPreferences(this).getFontStyle().getBaseId()); getSupportActionBar().setDisplayHomeAsUpEnabled(true); diff --git a/app/src/noGPlay/java/me/edgan/redditslide/ui/settings/SettingsBackup.java b/app/src/noGPlay/java/me/edgan/redditslide/ui/settings/SettingsBackup.java index 6ec833cd3..2116f5526 100644 --- a/app/src/noGPlay/java/me/edgan/redditslide/ui/settings/SettingsBackup.java +++ b/app/src/noGPlay/java/me/edgan/redditslide/ui/settings/SettingsBackup.java @@ -7,6 +7,7 @@ import android.os.Bundle; import android.provider.DocumentsContract; import android.util.Log; +import android.util.TypedValue; import androidx.appcompat.app.AlertDialog; import androidx.documentfile.provider.DocumentFile; @@ -56,6 +57,13 @@ protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); applyColorTheme(); setContentView(R.layout.activity_settings_sync); + + if (SettingValues.oldSwipeMode) { + TypedValue typedValue = new TypedValue(); + getTheme().resolveAttribute(R.attr.card_background, typedValue, true); + getWindow().getDecorView().setBackgroundColor(typedValue.data); + } + setupAppBar(R.id.toolbar, R.string.settings_title_backup, true, true); // Set up the local backup/restore UI diff --git a/app/src/withGPlay/java/me/edgan/redditslide/ui/settings/SettingsBackup.java b/app/src/withGPlay/java/me/edgan/redditslide/ui/settings/SettingsBackup.java index 09b13a99d..fe6598a42 100644 --- a/app/src/withGPlay/java/me/edgan/redditslide/ui/settings/SettingsBackup.java +++ b/app/src/withGPlay/java/me/edgan/redditslide/ui/settings/SettingsBackup.java @@ -7,6 +7,7 @@ import android.os.Bundle; import android.provider.DocumentsContract; import android.util.Log; +import android.util.TypedValue; import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; @@ -34,6 +35,7 @@ import me.edgan.redditslide.Activities.BaseActivityAnim; import me.edgan.redditslide.R; +import me.edgan.redditslide.SettingValues; import me.edgan.redditslide.util.LayoutUtils; import me.edgan.redditslide.util.StorageUtil; @@ -97,6 +99,13 @@ protected void onCreate(Bundle savedInstanceState) { applyColorTheme(); setContentView(R.layout.activity_settings_sync); + + if (SettingValues.oldSwipeMode) { + TypedValue typedValue = new TypedValue(); + getTheme().resolveAttribute(R.attr.card_background, typedValue, true); + getWindow().getDecorView().setBackgroundColor(typedValue.data); + } + setupAppBar(R.id.toolbar, R.string.settings_title_backup, true, true); // Initialize Google sign-in From 926d6cc9ea993c2c609b7341ce20deb4e0f0eb82 Mon Sep 17 00:00:00 2001 From: Nathan Grennan Date: Fri, 23 May 2025 03:15:27 -0700 Subject: [PATCH 67/99] Fixed the cascade effect for more screens --- .../me/edgan/redditslide/Activities/Announcement.java | 8 ++++++++ .../edgan/redditslide/Activities/CommentSearch.java | 8 ++++++++ .../me/edgan/redditslide/Activities/CreateMulti.java | 9 +++++++++ .../me/edgan/redditslide/Activities/Crosspost.java | 8 ++++++++ .../java/me/edgan/redditslide/Activities/Draw.java | 9 +++++++++ .../edgan/redditslide/Activities/ForceTouchLink.java | 8 ++++++++ .../edgan/redditslide/Activities/FullscreenVideo.java | 8 ++++++++ .../java/me/edgan/redditslide/Activities/Gallery.java | 9 +++++++++ .../me/edgan/redditslide/Activities/LiveThread.java | 9 +++++++++ .../java/me/edgan/redditslide/Activities/Loader.java | 9 +++++++++ .../java/me/edgan/redditslide/Activities/Login.java | 9 +++++++++ .../me/edgan/redditslide/Activities/ModQueue.java | 9 +++++++++ .../redditslide/Activities/MultiredditOverview.java | 8 ++++++++ .../me/edgan/redditslide/Activities/NewsActivity.java | 7 +++++++ .../me/edgan/redditslide/Activities/OpenContent.java | 9 +++++++++ .../me/edgan/redditslide/Activities/ReaderMode.java | 8 ++++++++ .../edgan/redditslide/Activities/Reauthenticate.java | 9 +++++++++ .../java/me/edgan/redditslide/Activities/Related.java | 9 +++++++++ .../java/me/edgan/redditslide/Activities/Search.java | 9 +++++++++ .../me/edgan/redditslide/Activities/SendMessage.java | 8 ++++++++ .../Activities/SettingsGeneralActivity.java | 11 +++++++++++ .../me/edgan/redditslide/Activities/SetupWidget.java | 9 +++++++++ .../me/edgan/redditslide/Activities/Shadowbox.java | 7 +++++++ .../redditslide/Activities/ShadowboxComments.java | 8 ++++++++ .../me/edgan/redditslide/Activities/Shortcut.java | 9 +++++++++ .../java/me/edgan/redditslide/Activities/Submit.java | 8 ++++++++ .../edgan/redditslide/Activities/SubredditSearch.java | 9 +++++++++ .../me/edgan/redditslide/Activities/Tutorial.java | 7 +++++++ .../java/me/edgan/redditslide/Activities/Website.java | 8 ++++++++ .../java/me/edgan/redditslide/Activities/Wiki.java | 9 +++++++++ 30 files changed, 255 insertions(+) diff --git a/app/src/main/java/me/edgan/redditslide/Activities/Announcement.java b/app/src/main/java/me/edgan/redditslide/Activities/Announcement.java index de9805d18..60f38731f 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/Announcement.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/Announcement.java @@ -1,6 +1,7 @@ package me.edgan.redditslide.Activities; import android.os.Bundle; +import android.util.TypedValue; import android.view.View; import android.view.Window; import android.widget.HorizontalScrollView; @@ -10,6 +11,7 @@ import me.edgan.redditslide.OpenRedditLink; import me.edgan.redditslide.R; import me.edgan.redditslide.Reddit; +import me.edgan.redditslide.SettingValues; import me.edgan.redditslide.SpoilerRobotoTextView; import me.edgan.redditslide.Views.CommentOverflow; import me.edgan.redditslide.Views.SidebarLayout; @@ -38,6 +40,12 @@ public void onCreate(Bundle savedInstance) { super.onCreate(savedInstance); setContentView(R.layout.submission_dialog); + if (SettingValues.oldSwipeMode) { + TypedValue typedValue = new TypedValue(); + getTheme().resolveAttribute(R.attr.card_background, typedValue, true); + getWindow().getDecorView().setBackgroundColor(typedValue.data); + } + SpoilerRobotoTextView spoilerRobotoTextView = (SpoilerRobotoTextView) findViewById(R.id.submission_dialog_firstTextView); CommentOverflow commentOverflow = diff --git a/app/src/main/java/me/edgan/redditslide/Activities/CommentSearch.java b/app/src/main/java/me/edgan/redditslide/Activities/CommentSearch.java index 52c110f89..9f9d87dec 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/CommentSearch.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/CommentSearch.java @@ -2,6 +2,7 @@ import android.os.Bundle; import android.text.Editable; +import android.util.TypedValue; import android.widget.EditText; import androidx.recyclerview.widget.RecyclerView; @@ -11,6 +12,7 @@ import me.edgan.redditslide.Adapters.CommentObject; import me.edgan.redditslide.DataShare; import me.edgan.redditslide.R; +import me.edgan.redditslide.SettingValues; import me.edgan.redditslide.Views.PreCachingLayoutManager; import me.edgan.redditslide.util.stubs.SimpleTextWatcher; @@ -35,6 +37,12 @@ public void onCreate(Bundle savedInstance) { applyColorTheme(); setContentView(R.layout.activity_filtercomments); + if (SettingValues.oldSwipeMode) { + TypedValue typedValue = new TypedValue(); + getTheme().resolveAttribute(R.attr.card_background, typedValue, true); + getWindow().getDecorView().setBackgroundColor(typedValue.data); + } + final EditText search = (EditText) findViewById(R.id.search); RecyclerView rv = (RecyclerView) findViewById(R.id.vertical_content); final PreCachingLayoutManager mLayoutManager = new PreCachingLayoutManager(this); diff --git a/app/src/main/java/me/edgan/redditslide/Activities/CreateMulti.java b/app/src/main/java/me/edgan/redditslide/Activities/CreateMulti.java index bce0791a1..ec392dac0 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/CreateMulti.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/CreateMulti.java @@ -20,6 +20,7 @@ import android.os.AsyncTask; import android.os.Bundle; import android.util.Log; +import android.util.TypedValue; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; @@ -40,6 +41,7 @@ import me.edgan.redditslide.Authentication; import me.edgan.redditslide.R; +import me.edgan.redditslide.SettingValues; import me.edgan.redditslide.UserSubscriptions; import me.edgan.redditslide.Visuals.Palette; import me.edgan.redditslide.util.BlendModeUtil; @@ -81,6 +83,13 @@ protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); applyColorTheme(); setContentView(R.layout.activity_createmulti); + + if (SettingValues.oldSwipeMode) { + TypedValue typedValue = new TypedValue(); + getTheme().resolveAttribute(R.attr.card_background, typedValue, true); + getWindow().getDecorView().setBackgroundColor(typedValue.data); + } + setupAppBar(R.id.toolbar, "", true, true); findViewById(R.id.add) diff --git a/app/src/main/java/me/edgan/redditslide/Activities/Crosspost.java b/app/src/main/java/me/edgan/redditslide/Activities/Crosspost.java index b472f8169..374169f41 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/Crosspost.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/Crosspost.java @@ -3,6 +3,7 @@ import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; +import android.util.TypedValue; import android.view.View; import android.view.Window; import android.view.WindowManager; @@ -18,6 +19,7 @@ import me.edgan.redditslide.Authentication; import me.edgan.redditslide.OpenRedditLink; import me.edgan.redditslide.R; +import me.edgan.redditslide.SettingValues; import me.edgan.redditslide.SpoilerRobotoTextView; import me.edgan.redditslide.UserSubscriptions; import me.edgan.redditslide.Views.CommentOverflow; @@ -45,6 +47,12 @@ public void onCreate(Bundle savedInstanceState) { applyColorTheme(); setContentView(R.layout.activity_crosspost); + if (SettingValues.oldSwipeMode) { + TypedValue typedValue = new TypedValue(); + getTheme().resolveAttribute(R.attr.card_background, typedValue, true); + getWindow().getDecorView().setBackgroundColor(typedValue.data); + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { Window window = this.getWindow(); window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); diff --git a/app/src/main/java/me/edgan/redditslide/Activities/Draw.java b/app/src/main/java/me/edgan/redditslide/Activities/Draw.java index 26e12cc48..ee56d3694 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/Draw.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/Draw.java @@ -5,6 +5,7 @@ import android.graphics.Color; import android.net.Uri; import android.os.Bundle; +import android.util.TypedValue; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; @@ -23,6 +24,7 @@ import me.edgan.redditslide.R; import me.edgan.redditslide.Reddit; +import me.edgan.redditslide.SettingValues; import me.edgan.redditslide.Views.CanvasView; import me.edgan.redditslide.Views.DoEditorActions; import me.edgan.redditslide.Visuals.Palette; @@ -52,6 +54,13 @@ public void onCreate(Bundle savedInstance) { super.onCreate(savedInstance); applyColorTheme(""); setContentView(R.layout.activity_draw); + + if (SettingValues.oldSwipeMode) { + TypedValue typedValue = new TypedValue(); + getTheme().resolveAttribute(R.attr.card_background, typedValue, true); + getWindow().getDecorView().setBackgroundColor(typedValue.data); + } + drawView = (CanvasView) findViewById(R.id.paintView); drawView.setBaseColor(Color.parseColor("#303030")); color = findViewById(R.id.color); diff --git a/app/src/main/java/me/edgan/redditslide/Activities/ForceTouchLink.java b/app/src/main/java/me/edgan/redditslide/Activities/ForceTouchLink.java index 0a077256c..84ad634ea 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/ForceTouchLink.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/ForceTouchLink.java @@ -1,6 +1,7 @@ package me.edgan.redditslide.Activities; import android.os.Bundle; +import android.util.TypedValue; import android.view.MotionEvent; import android.view.View; import android.view.Window; @@ -9,6 +10,7 @@ import me.edgan.redditslide.ContentType; import me.edgan.redditslide.R; import me.edgan.redditslide.Reddit; +import me.edgan.redditslide.SettingValues; import me.edgan.redditslide.Views.ExoVideoView; import me.edgan.redditslide.util.GifUtils; @@ -30,6 +32,12 @@ public void onCreate(Bundle savedInstance) { setContentView(R.layout.activity_force_touch_content); + if (SettingValues.oldSwipeMode) { + TypedValue typedValue = new TypedValue(); + getTheme().resolveAttribute(R.attr.card_background, typedValue, true); + getWindow().getDecorView().setBackgroundColor(typedValue.data); + } + findViewById(android.R.id.content) .setOnTouchListener( new View.OnTouchListener() { diff --git a/app/src/main/java/me/edgan/redditslide/Activities/FullscreenVideo.java b/app/src/main/java/me/edgan/redditslide/Activities/FullscreenVideo.java index e2800ebec..bbae2359d 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/FullscreenVideo.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/FullscreenVideo.java @@ -3,6 +3,7 @@ import android.graphics.Color; import android.os.Build; import android.os.Bundle; +import android.util.TypedValue; import android.view.Window; import android.view.WindowManager; import android.webkit.WebChromeClient; @@ -13,6 +14,7 @@ import me.edgan.redditslide.R; import me.edgan.redditslide.Reddit; +import me.edgan.redditslide.SettingValues; import me.edgan.redditslide.util.LinkUtil; import me.edgan.redditslide.util.LogUtil; @@ -35,6 +37,12 @@ public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_video); + if (SettingValues.oldSwipeMode) { + TypedValue typedValue = new TypedValue(); + getTheme().resolveAttribute(R.attr.card_background, typedValue, true); + getWindow().getDecorView().setBackgroundColor(typedValue.data); + } + String data = getIntent().getExtras().getString(EXTRA_HTML); v = (WebView) findViewById(R.id.webgif); diff --git a/app/src/main/java/me/edgan/redditslide/Activities/Gallery.java b/app/src/main/java/me/edgan/redditslide/Activities/Gallery.java index a59652d07..173e873e0 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/Gallery.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/Gallery.java @@ -2,6 +2,7 @@ import android.content.res.Configuration; import android.os.Bundle; +import android.util.TypedValue; import android.view.View; import androidx.annotation.NonNull; @@ -16,6 +17,7 @@ import me.edgan.redditslide.OfflineSubreddit; import me.edgan.redditslide.PostLoader; import me.edgan.redditslide.R; +import me.edgan.redditslide.SettingValues; import me.edgan.redditslide.Views.CatchStaggeredGridLayoutManager; import me.edgan.redditslide.util.LayoutUtils; @@ -51,6 +53,13 @@ public void onCreate(Bundle savedInstance) { applyDarkColorTheme(subreddit); super.onCreate(savedInstance); setContentView(R.layout.gallery); + + if (SettingValues.oldSwipeMode) { + TypedValue typedValue = new TypedValue(); + getTheme().resolveAttribute(R.attr.card_background, typedValue, true); + getWindow().getDecorView().setBackgroundColor(typedValue.data); + } + getWindow() .getDecorView() .setSystemUiVisibility( diff --git a/app/src/main/java/me/edgan/redditslide/Activities/LiveThread.java b/app/src/main/java/me/edgan/redditslide/Activities/LiveThread.java index fbe6925c0..c62dac439 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/LiveThread.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/LiveThread.java @@ -6,6 +6,7 @@ import android.graphics.drawable.ColorDrawable; import android.os.AsyncTask; import android.os.Bundle; +import android.util.TypedValue; import android.view.Gravity; import android.view.LayoutInflater; import android.view.Menu; @@ -39,6 +40,7 @@ import me.edgan.redditslide.ContentType; import me.edgan.redditslide.R; import me.edgan.redditslide.Reddit; +import me.edgan.redditslide.SettingValues; import me.edgan.redditslide.SpoilerRobotoTextView; import me.edgan.redditslide.Views.CommentOverflow; import me.edgan.redditslide.Views.SidebarLayout; @@ -108,6 +110,13 @@ public void onCreate(Bundle savedInstanceState) { applyColorTheme(); setContentView(R.layout.activity_livethread); + + if (SettingValues.oldSwipeMode) { + TypedValue typedValue = new TypedValue(); + getTheme().resolveAttribute(R.attr.card_background, typedValue, true); + getWindow().getDecorView().setBackgroundColor(typedValue.data); + } + baseRecycler = (RecyclerView) findViewById(R.id.content_view); baseRecycler.setLayoutManager(new LinearLayoutManager(LiveThread.this)); new AsyncTask() { diff --git a/app/src/main/java/me/edgan/redditslide/Activities/Loader.java b/app/src/main/java/me/edgan/redditslide/Activities/Loader.java index 020257998..97ad6dcac 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/Loader.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/Loader.java @@ -2,8 +2,10 @@ /** Created by carlo_000 on 1/20/2016. */ import android.os.Bundle; +import android.util.TypedValue; import me.edgan.redditslide.R; +import me.edgan.redditslide.SettingValues; /** Created by ccrama on 9/17/2015. */ public class Loader extends BaseActivity { @@ -14,6 +16,13 @@ public void onCreate(Bundle savedInstance) { super.onCreate(savedInstance); applyColorTheme(); setContentView(R.layout.activity_loading); + + if (SettingValues.oldSwipeMode) { + TypedValue typedValue = new TypedValue(); + getTheme().resolveAttribute(R.attr.card_background, typedValue, true); + getWindow().getDecorView().setBackgroundColor(typedValue.data); + } + MainActivity.loader = this; } } diff --git a/app/src/main/java/me/edgan/redditslide/Activities/Login.java b/app/src/main/java/me/edgan/redditslide/Activities/Login.java index 6848247f8..97194d60f 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/Login.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/Login.java @@ -8,6 +8,7 @@ import android.os.Build; import android.os.Bundle; import android.util.Log; +import android.util.TypedValue; import android.view.View; import android.webkit.CookieManager; import android.webkit.CookieSyncManager; @@ -24,6 +25,7 @@ import me.edgan.redditslide.Constants; import me.edgan.redditslide.R; import me.edgan.redditslide.Reddit; +import me.edgan.redditslide.SettingValues; import me.edgan.redditslide.UserSubscriptions; import me.edgan.redditslide.Visuals.GetClosestColor; import me.edgan.redditslide.Visuals.Palette; @@ -61,6 +63,13 @@ public void onCreate(Bundle savedInstance) { finish(); return; } + + if (SettingValues.oldSwipeMode) { + TypedValue typedValue = new TypedValue(); + getTheme().resolveAttribute(R.attr.card_background, typedValue, true); + getWindow().getDecorView().setBackgroundColor(typedValue.data); + } + setupAppBar(R.id.toolbar, R.string.title_login, true, true); String[] scopes = { diff --git a/app/src/main/java/me/edgan/redditslide/Activities/ModQueue.java b/app/src/main/java/me/edgan/redditslide/Activities/ModQueue.java index a8cdcc93d..1a9b0bfd5 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/ModQueue.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/ModQueue.java @@ -1,6 +1,7 @@ package me.edgan.redditslide.Activities; import android.os.Bundle; +import android.util.TypedValue; import android.view.View; import android.view.ViewGroup; import android.view.animation.LinearInterpolator; @@ -17,6 +18,7 @@ import me.edgan.redditslide.Fragments.ModLog; import me.edgan.redditslide.Fragments.ModPage; import me.edgan.redditslide.R; +import me.edgan.redditslide.SettingValues; import me.edgan.redditslide.UserSubscriptions; import me.edgan.redditslide.Visuals.ColorPreferences; import me.edgan.redditslide.Visuals.Palette; @@ -34,6 +36,13 @@ public void onCreate(Bundle savedInstance) { applyColorTheme(""); setContentView(R.layout.activity_inbox); + + if (SettingValues.oldSwipeMode) { + TypedValue typedValue = new TypedValue(); + getTheme().resolveAttribute(R.attr.card_background, typedValue, true); + getWindow().getDecorView().setBackgroundColor(typedValue.data); + } + setupAppBar(R.id.toolbar, R.string.drawer_moderation, true, true); TabLayout tabs = (TabLayout) findViewById(R.id.sliding_tabs); diff --git a/app/src/main/java/me/edgan/redditslide/Activities/MultiredditOverview.java b/app/src/main/java/me/edgan/redditslide/Activities/MultiredditOverview.java index 003ba7dbc..409e1e96c 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/MultiredditOverview.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/MultiredditOverview.java @@ -8,6 +8,7 @@ import android.os.Bundle; import android.text.Spannable; import android.util.Log; +import android.util.TypedValue; import android.view.Gravity; import android.view.KeyEvent; import android.view.Menu; @@ -335,6 +336,13 @@ public void onCreate(Bundle savedInstance) { applyColorTheme(""); setContentView(R.layout.activity_multireddits); + + if (SettingValues.oldSwipeMode) { + TypedValue typedValue = new TypedValue(); + getTheme().resolveAttribute(R.attr.card_background, typedValue, true); + getWindow().getDecorView().setBackgroundColor(typedValue.data); + } + setupAppBar(R.id.toolbar, R.string.title_multireddits, true, false); findViewById(R.id.header).setBackgroundColor(Palette.getDefaultColor()); diff --git a/app/src/main/java/me/edgan/redditslide/Activities/NewsActivity.java b/app/src/main/java/me/edgan/redditslide/Activities/NewsActivity.java index e1dac7be9..43a9d23d4 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/NewsActivity.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/NewsActivity.java @@ -11,6 +11,7 @@ import android.os.Build; import android.os.Bundle; import android.os.Parcelable; +import android.util.TypedValue; import android.view.View; import android.view.ViewGroup; import android.view.Window; @@ -143,6 +144,12 @@ protected void onCreate(final Bundle savedInstanceState) { setContentView(R.layout.activity_news); + if (SettingValues.oldSwipeMode) { + TypedValue typedValue = new TypedValue(); + getTheme().resolveAttribute(R.attr.card_background, typedValue, true); + getWindow().getDecorView().setBackgroundColor(typedValue.data); + } + mToolbar = (Toolbar) findViewById(R.id.toolbar); mToolbar.setPopupTheme(new ColorPreferences(this).getFontStyle().getBaseId()); setSupportActionBar(mToolbar); diff --git a/app/src/main/java/me/edgan/redditslide/Activities/OpenContent.java b/app/src/main/java/me/edgan/redditslide/Activities/OpenContent.java index ceeb9853f..14df90f6c 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/OpenContent.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/OpenContent.java @@ -6,10 +6,12 @@ import android.os.Build; import android.os.Bundle; import android.util.Log; +import android.util.TypedValue; import android.widget.Toast; import me.edgan.redditslide.OpenRedditLink; import me.edgan.redditslide.R; +import me.edgan.redditslide.SettingValues; import me.edgan.redditslide.util.LogUtil; import java.util.Locale; @@ -23,6 +25,13 @@ public class OpenContent extends Activity { public void onCreate(Bundle savedInstance) { super.onCreate(savedInstance); setContentView(R.layout.clear); + + if (SettingValues.oldSwipeMode) { + TypedValue typedValue = new TypedValue(); + getTheme().resolveAttribute(R.attr.card_background, typedValue, true); + getWindow().getDecorView().setBackgroundColor(typedValue.data); + } + Intent intent = getIntent(); Uri data = intent.getData(); Bundle extras = intent.getExtras(); diff --git a/app/src/main/java/me/edgan/redditslide/Activities/ReaderMode.java b/app/src/main/java/me/edgan/redditslide/Activities/ReaderMode.java index 6d3987d9e..f16228476 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/ReaderMode.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/ReaderMode.java @@ -3,6 +3,7 @@ import android.content.Intent; import android.os.AsyncTask; import android.os.Bundle; +import android.util.TypedValue; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; @@ -16,6 +17,7 @@ import me.edgan.redditslide.Constants; import me.edgan.redditslide.R; import me.edgan.redditslide.Reddit; +import me.edgan.redditslide.SettingValues; import me.edgan.redditslide.SpoilerRobotoTextView; import me.edgan.redditslide.Visuals.Palette; import me.edgan.redditslide.util.LinkUtil; @@ -38,6 +40,12 @@ public void onCreate(Bundle savedInstanceState) { applyColorTheme(""); setContentView(R.layout.activity_reader); + if (SettingValues.oldSwipeMode) { + TypedValue typedValue = new TypedValue(); + getTheme().resolveAttribute(R.attr.card_background, typedValue, true); + getWindow().getDecorView().setBackgroundColor(typedValue.data); + } + mSubredditColor = getIntent().getExtras().getInt(LinkUtil.EXTRA_COLOR, Palette.getDefaultColor()); diff --git a/app/src/main/java/me/edgan/redditslide/Activities/Reauthenticate.java b/app/src/main/java/me/edgan/redditslide/Activities/Reauthenticate.java index 745f89c63..f18065644 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/Reauthenticate.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/Reauthenticate.java @@ -6,6 +6,7 @@ import android.os.Build; import android.os.Bundle; import android.util.Log; +import android.util.TypedValue; import android.view.View; import android.webkit.CookieManager; import android.webkit.WebChromeClient; @@ -20,6 +21,7 @@ import me.edgan.redditslide.Constants; import me.edgan.redditslide.R; import me.edgan.redditslide.Reddit; +import me.edgan.redditslide.SettingValues; import me.edgan.redditslide.util.LogUtil; import net.dean.jraw.http.NetworkException; @@ -39,6 +41,13 @@ public void onCreate(Bundle savedInstance) { super.onCreate(savedInstance); applyColorTheme(""); setContentView(R.layout.activity_login); + + if (SettingValues.oldSwipeMode) { + TypedValue typedValue = new TypedValue(); + getTheme().resolveAttribute(R.attr.card_background, typedValue, true); + getWindow().getDecorView().setBackgroundColor(typedValue.data); + } + setupAppBar(R.id.toolbar, "Re-authenticate", true, true); String[] scopes = { diff --git a/app/src/main/java/me/edgan/redditslide/Activities/Related.java b/app/src/main/java/me/edgan/redditslide/Activities/Related.java index 9bc8416e8..003e3daf0 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/Related.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/Related.java @@ -2,6 +2,7 @@ import android.content.Intent; import android.os.Bundle; +import android.util.TypedValue; import android.view.MenuItem; import androidx.annotation.NonNull; @@ -13,6 +14,7 @@ import me.edgan.redditslide.Adapters.SubredditSearchPosts; import me.edgan.redditslide.Constants; import me.edgan.redditslide.R; +import me.edgan.redditslide.SettingValues; import me.edgan.redditslide.Views.CatchStaggeredGridLayoutManager; import me.edgan.redditslide.Views.PreCachingLayoutManager; import me.edgan.redditslide.Visuals.ColorPreferences; @@ -48,6 +50,13 @@ public void onCreate(Bundle savedInstanceState) { applyColorTheme(""); setContentView(R.layout.activity_search); + + if (SettingValues.oldSwipeMode) { + TypedValue typedValue = new TypedValue(); + getTheme().resolveAttribute(R.attr.card_background, typedValue, true); + getWindow().getDecorView().setBackgroundColor(typedValue.data); + } + Intent intent = getIntent(); if (intent.hasExtra(Intent.EXTRA_TEXT) && !intent.getExtras().getString(Intent.EXTRA_TEXT, "").isEmpty()) { diff --git a/app/src/main/java/me/edgan/redditslide/Activities/Search.java b/app/src/main/java/me/edgan/redditslide/Activities/Search.java index 444d876f9..ab0421de9 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/Search.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/Search.java @@ -4,6 +4,7 @@ import android.content.Intent; import android.content.res.Configuration; import android.os.Bundle; +import android.util.TypedValue; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; @@ -21,6 +22,7 @@ import me.edgan.redditslide.Adapters.SubredditSearchPosts; import me.edgan.redditslide.Constants; import me.edgan.redditslide.R; +import me.edgan.redditslide.SettingValues; import me.edgan.redditslide.Views.CatchStaggeredGridLayoutManager; import me.edgan.redditslide.Views.PreCachingLayoutManager; import me.edgan.redditslide.Visuals.ColorPreferences; @@ -242,6 +244,13 @@ public void onCreate(Bundle savedInstanceState) { applyColorTheme(""); setContentView(R.layout.activity_search); + + if (SettingValues.oldSwipeMode) { + TypedValue typedValue = new TypedValue(); + getTheme().resolveAttribute(R.attr.card_background, typedValue, true); + getWindow().getDecorView().setBackgroundColor(typedValue.data); + } + where = getIntent().getExtras().getString(EXTRA_TERM, ""); time = TimePeriod.ALL; diff --git a/app/src/main/java/me/edgan/redditslide/Activities/SendMessage.java b/app/src/main/java/me/edgan/redditslide/Activities/SendMessage.java index 533cd2087..69e9c09d6 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/SendMessage.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/SendMessage.java @@ -4,6 +4,7 @@ import android.os.Build; import android.os.Bundle; import android.text.InputType; +import android.util.TypedValue; import android.view.View; import android.view.Window; import android.view.WindowManager; @@ -20,6 +21,7 @@ import me.edgan.redditslide.Authentication; import me.edgan.redditslide.DataShare; import me.edgan.redditslide.R; +import me.edgan.redditslide.SettingValues; import me.edgan.redditslide.UserSubscriptions; import me.edgan.redditslide.Views.DoEditorActions; import me.edgan.redditslide.Visuals.Palette; @@ -61,6 +63,12 @@ public void onCreate(Bundle savedInstanceState) { applyColorTheme(); setContentView(R.layout.activity_sendmessage); + if (SettingValues.oldSwipeMode) { + TypedValue typedValue = new TypedValue(); + getTheme().resolveAttribute(R.attr.card_background, typedValue, true); + getWindow().getDecorView().setBackgroundColor(typedValue.data); + } + final Toolbar b = (Toolbar) findViewById(R.id.toolbar); final String name; reply = getIntent() != null && getIntent().hasExtra(EXTRA_REPLY); diff --git a/app/src/main/java/me/edgan/redditslide/Activities/SettingsGeneralActivity.java b/app/src/main/java/me/edgan/redditslide/Activities/SettingsGeneralActivity.java index 2d8bc032f..b86ff2abc 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/SettingsGeneralActivity.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/SettingsGeneralActivity.java @@ -2,11 +2,15 @@ import android.content.pm.PackageManager; import android.os.Bundle; +import android.util.TypedValue; + import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; + import android.widget.Toast; import me.edgan.redditslide.R; +import me.edgan.redditslide.SettingValues; import me.edgan.redditslide.util.LogUtil; public class SettingsGeneralActivity extends AppCompatActivity { @@ -17,6 +21,13 @@ public class SettingsGeneralActivity extends AppCompatActivity { protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_settings_general); + + if (SettingValues.oldSwipeMode) { + TypedValue typedValue = new TypedValue(); + getTheme().resolveAttribute(R.attr.card_background, typedValue, true); + getWindow().getDecorView().setBackgroundColor(typedValue.data); + } + } @Override diff --git a/app/src/main/java/me/edgan/redditslide/Activities/SetupWidget.java b/app/src/main/java/me/edgan/redditslide/Activities/SetupWidget.java index 65d8afa98..517dc1ecf 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/SetupWidget.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/SetupWidget.java @@ -6,6 +6,7 @@ import android.content.Intent; import android.os.Bundle; import android.text.Editable; +import android.util.TypedValue; import android.view.View; import android.widget.EditText; import android.widget.ListView; @@ -15,6 +16,7 @@ import me.edgan.redditslide.Adapters.SubChooseAdapter; import me.edgan.redditslide.R; +import me.edgan.redditslide.SettingValues; import me.edgan.redditslide.UserSubscriptions; import me.edgan.redditslide.Visuals.ColorPreferences; import me.edgan.redditslide.Visuals.FontPreferences; @@ -60,6 +62,13 @@ private void assignAppWidgetId() { public void doShortcut() { setContentView(R.layout.activity_setup_widget); + + if (SettingValues.oldSwipeMode) { + TypedValue typedValue = new TypedValue(); + getTheme().resolveAttribute(R.attr.card_background, typedValue, true); + getWindow().getDecorView().setBackgroundColor(typedValue.data); + } + setupAppBar(R.id.toolbar, R.string.widget_creation_title, true, true); header = getLayoutInflater().inflate(R.layout.widget_header, null); diff --git a/app/src/main/java/me/edgan/redditslide/Activities/Shadowbox.java b/app/src/main/java/me/edgan/redditslide/Activities/Shadowbox.java index f6e871cec..b0d5b6f44 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/Shadowbox.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/Shadowbox.java @@ -1,6 +1,7 @@ package me.edgan.redditslide.Activities; import android.os.Bundle; +import android.util.TypedValue; import androidx.annotation.NonNull; import androidx.fragment.app.Fragment; @@ -63,6 +64,12 @@ public void onCreate(Bundle savedInstance) { super.onCreate(savedInstance); setContentView(R.layout.activity_slide); + if (SettingValues.oldSwipeMode) { + TypedValue typedValue = new TypedValue(); + getTheme().resolveAttribute(R.attr.card_background, typedValue, true); + getWindow().getDecorView().setBackgroundColor(typedValue.data); + } + long offline = getIntent().getLongExtra("offline", 0L); OfflineSubreddit submissions = diff --git a/app/src/main/java/me/edgan/redditslide/Activities/ShadowboxComments.java b/app/src/main/java/me/edgan/redditslide/Activities/ShadowboxComments.java index 916e669f1..0dcccdf68 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/ShadowboxComments.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/ShadowboxComments.java @@ -1,6 +1,7 @@ package me.edgan.redditslide.Activities; import android.os.Bundle; +import android.util.TypedValue; import androidx.annotation.NonNull; import androidx.fragment.app.Fragment; @@ -13,6 +14,7 @@ import me.edgan.redditslide.Fragments.AlbumFullComments; import me.edgan.redditslide.Fragments.MediaFragmentComment; import me.edgan.redditslide.R; +import me.edgan.redditslide.SettingValues; import net.dean.jraw.models.Comment; @@ -33,6 +35,12 @@ public void onCreate(Bundle savedInstance) { super.onCreate(savedInstance); setContentView(R.layout.activity_slide); + if (SettingValues.oldSwipeMode) { + TypedValue typedValue = new TypedValue(); + getTheme().resolveAttribute(R.attr.card_background, typedValue, true); + getWindow().getDecorView().setBackgroundColor(typedValue.data); + } + ViewPager pager = (ViewPager) findViewById(R.id.content_view); commentPager = new ShadowboxCommentsPagerAdapter(getSupportFragmentManager()); pager.setAdapter(commentPager); diff --git a/app/src/main/java/me/edgan/redditslide/Activities/Shortcut.java b/app/src/main/java/me/edgan/redditslide/Activities/Shortcut.java index 1695ff533..7d94edb5c 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/Shortcut.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/Shortcut.java @@ -2,12 +2,14 @@ import android.os.Bundle; import android.text.Editable; +import android.util.TypedValue; import android.view.View; import android.widget.EditText; import android.widget.ListView; import me.edgan.redditslide.Adapters.SubChooseAdapter; import me.edgan.redditslide.R; +import me.edgan.redditslide.SettingValues; import me.edgan.redditslide.UserSubscriptions; import me.edgan.redditslide.Visuals.ColorPreferences; import me.edgan.redditslide.Visuals.FontPreferences; @@ -33,6 +35,13 @@ protected void onCreate(Bundle savedInstanceState) { public void doShortcut() { setContentView(R.layout.activity_setup_widget); + + if (SettingValues.oldSwipeMode) { + TypedValue typedValue = new TypedValue(); + getTheme().resolveAttribute(R.attr.card_background, typedValue, true); + getWindow().getDecorView().setBackgroundColor(typedValue.data); + } + setupAppBar(R.id.toolbar, R.string.shortcut_creation_title, true, true); header = getLayoutInflater().inflate(R.layout.shortcut_header, null); ListView list = (ListView) findViewById(R.id.subs); diff --git a/app/src/main/java/me/edgan/redditslide/Activities/Submit.java b/app/src/main/java/me/edgan/redditslide/Activities/Submit.java index b74fb5889..4ef06abf3 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/Submit.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/Submit.java @@ -7,6 +7,7 @@ import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; +import android.util.TypedValue; import android.view.View; import android.view.Window; import android.view.WindowManager; @@ -41,6 +42,7 @@ import me.edgan.redditslide.OpenRedditLink; import me.edgan.redditslide.R; import me.edgan.redditslide.Reddit; +import me.edgan.redditslide.SettingValues; import me.edgan.redditslide.SpoilerRobotoTextView; import me.edgan.redditslide.UserSubscriptions; import me.edgan.redditslide.Views.CommentOverflow; @@ -110,6 +112,12 @@ public void onCreate(Bundle savedInstanceState) { applyColorTheme(); setContentView(R.layout.activity_submit); + if (SettingValues.oldSwipeMode) { + TypedValue typedValue = new TypedValue(); + getTheme().resolveAttribute(R.attr.card_background, typedValue, true); + getWindow().getDecorView().setBackgroundColor(typedValue.data); + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { Window window = this.getWindow(); window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); diff --git a/app/src/main/java/me/edgan/redditslide/Activities/SubredditSearch.java b/app/src/main/java/me/edgan/redditslide/Activities/SubredditSearch.java index b2f41a6c8..2b9beaa6b 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/SubredditSearch.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/SubredditSearch.java @@ -2,6 +2,7 @@ import android.content.Intent; import android.os.Bundle; +import android.util.TypedValue; import android.text.InputType; import android.view.Menu; import android.view.MenuInflater; @@ -17,6 +18,7 @@ import me.edgan.redditslide.Fragments.SubredditListView; import me.edgan.redditslide.R; +import me.edgan.redditslide.SettingValues; /** Created by ccrama on 9/17/2015. */ public class SubredditSearch extends BaseActivityAnim { @@ -85,6 +87,13 @@ public void onCreate(Bundle savedInstance) { term = getIntent().getExtras().getString("term"); applyColorTheme(""); setContentView(R.layout.activity_fragmentinner); + + if (SettingValues.oldSwipeMode) { + TypedValue typedValue = new TypedValue(); + getTheme().resolveAttribute(R.attr.card_background, typedValue, true); + getWindow().getDecorView().setBackgroundColor(typedValue.data); + } + setupAppBar(R.id.toolbar, term, true, true); Fragment f = new SubredditListView(); diff --git a/app/src/main/java/me/edgan/redditslide/Activities/Tutorial.java b/app/src/main/java/me/edgan/redditslide/Activities/Tutorial.java index cfef5aaae..0a108374e 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/Tutorial.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/Tutorial.java @@ -15,6 +15,7 @@ import android.text.Editable; import android.text.TextWatcher; import android.util.Pair; +import android.util.TypedValue; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -83,6 +84,12 @@ protected void onCreate(Bundle savedInstanceState) { binding = ActivityTutorialBinding.inflate(getLayoutInflater()); setContentView(binding.getRoot()); + if (SettingValues.oldSwipeMode) { + TypedValue typedValue = new TypedValue(); + getTheme().resolveAttribute(R.attr.card_background, typedValue, true); + getWindow().getDecorView().setBackgroundColor(typedValue.data); + } + // The pager adapter, which provides the pages to the view pager widget. binding.tutorialViewPager.setAdapter(new TutorialPagerAdapter(getSupportFragmentManager())); diff --git a/app/src/main/java/me/edgan/redditslide/Activities/Website.java b/app/src/main/java/me/edgan/redditslide/Activities/Website.java index 03c611a10..310f161e9 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/Website.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/Website.java @@ -4,6 +4,7 @@ import android.net.Uri; import android.os.Build; import android.os.Bundle; +import android.util.TypedValue; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; @@ -167,6 +168,13 @@ public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); applyColorTheme(""); setContentView(R.layout.activity_web); + + if (SettingValues.oldSwipeMode) { + TypedValue typedValue = new TypedValue(); + getTheme().resolveAttribute(R.attr.card_background, typedValue, true); + getWindow().getDecorView().setBackgroundColor(typedValue.data); + } + url = getIntent().getExtras().getString(LinkUtil.EXTRA_URL, ""); subredditColor = getIntent().getExtras().getInt(LinkUtil.EXTRA_COLOR, Palette.getDefaultColor()); diff --git a/app/src/main/java/me/edgan/redditslide/Activities/Wiki.java b/app/src/main/java/me/edgan/redditslide/Activities/Wiki.java index 1c959c430..f2be21e79 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/Wiki.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/Wiki.java @@ -4,6 +4,7 @@ import android.graphics.Color; import android.os.AsyncTask; import android.os.Bundle; +import android.util.TypedValue; import androidx.annotation.ColorInt; import androidx.annotation.NonNull; @@ -17,6 +18,7 @@ import me.edgan.redditslide.Authentication; import me.edgan.redditslide.Fragments.WikiPage; import me.edgan.redditslide.R; +import me.edgan.redditslide.SettingValues; import me.edgan.redditslide.Views.ToggleSwipeViewPager; import me.edgan.redditslide.Visuals.ColorPreferences; import me.edgan.redditslide.Visuals.Palette; @@ -54,6 +56,13 @@ public void onCreate(Bundle savedInstance) { createCustomCss(); createCustomJavaScript(); setContentView(R.layout.activity_slidetabs); + + if (SettingValues.oldSwipeMode) { + TypedValue typedValue = new TypedValue(); + getTheme().resolveAttribute(R.attr.card_background, typedValue, true); + getWindow().getDecorView().setBackgroundColor(typedValue.data); + } + setupSubredditAppBar(R.id.toolbar, "/r/" + subreddit + " wiki", true, subreddit); if (getIntent().hasExtra(EXTRA_PAGE)) { From fc8dc2b76f91ca4671c97ad521a86caa2ffcf9d1 Mon Sep 17 00:00:00 2001 From: Nathan Grennan Date: Fri, 23 May 2025 03:22:41 -0700 Subject: [PATCH 68/99] Fixed cascade effect for more screens --- .../edgan/redditslide/ui/settings/EditCardsLayout.java | 7 +++++++ .../me/edgan/redditslide/ui/settings/SettingsLibs.java | 9 +++++++++ .../edgan/redditslide/ui/settings/SettingsSubreddit.java | 8 ++++++++ .../edgan/redditslide/ui/settings/SettingsViewType.java | 8 ++++++++ 4 files changed, 32 insertions(+) diff --git a/app/src/main/java/me/edgan/redditslide/ui/settings/EditCardsLayout.java b/app/src/main/java/me/edgan/redditslide/ui/settings/EditCardsLayout.java index fce44f7e5..40391cb15 100644 --- a/app/src/main/java/me/edgan/redditslide/ui/settings/EditCardsLayout.java +++ b/app/src/main/java/me/edgan/redditslide/ui/settings/EditCardsLayout.java @@ -2,6 +2,7 @@ import android.content.SharedPreferences; import android.os.Bundle; +import android.util.TypedValue; import android.view.MenuItem; import android.view.View; import android.widget.CompoundButton; @@ -32,6 +33,12 @@ public void onCreate(Bundle savedInstance) { applyColorTheme(); setContentView(R.layout.activity_settings_theme_card); + if (SettingValues.oldSwipeMode) { + TypedValue typedValue = new TypedValue(); + getTheme().resolveAttribute(R.attr.card_background, typedValue, true); + getWindow().getDecorView().setBackgroundColor(typedValue.data); + } + setupAppBar(R.id.toolbar, R.string.settings_layout_default, true, true); final LinearLayout layout = (LinearLayout) findViewById(R.id.card); diff --git a/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsLibs.java b/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsLibs.java index eba6d53a4..56309f1c6 100644 --- a/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsLibs.java +++ b/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsLibs.java @@ -1,12 +1,14 @@ package me.edgan.redditslide.ui.settings; import android.os.Bundle; +import android.util.TypedValue; import com.mikepenz.aboutlibraries.LibsBuilder; import com.mikepenz.aboutlibraries.ui.LibsSupportFragment; import me.edgan.redditslide.Activities.BaseActivityAnim; import me.edgan.redditslide.R; +import me.edgan.redditslide.SettingValues; public class SettingsLibs extends BaseActivityAnim { @@ -15,6 +17,13 @@ protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); applyColorTheme(); setContentView(R.layout.activity_settings_libs); + + if (SettingValues.oldSwipeMode) { + TypedValue typedValue = new TypedValue(); + getTheme().resolveAttribute(R.attr.card_background, typedValue, true); + getWindow().getDecorView().setBackgroundColor(typedValue.data); + } + setupAppBar(R.id.toolbar, R.string.settings_about_libs, true, true); LibsSupportFragment fragment = new LibsBuilder().supportFragment(); diff --git a/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsSubreddit.java b/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsSubreddit.java index 2f5ac46ce..7f7e053ab 100644 --- a/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsSubreddit.java +++ b/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsSubreddit.java @@ -4,6 +4,7 @@ import android.content.res.Resources; import android.os.AsyncTask; import android.os.Bundle; +import android.util.TypedValue; import android.view.View; import androidx.appcompat.app.AlertDialog; @@ -59,6 +60,13 @@ public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); applyColorTheme(); setContentView(R.layout.activity_settings_subreddit); + + if (SettingValues.oldSwipeMode) { + TypedValue typedValue = new TypedValue(); + getTheme().resolveAttribute(R.attr.card_background, typedValue, true); + getWindow().getDecorView().setBackgroundColor(typedValue.data); + } + SettingsThemeFragment.changed = true; setupAppBar(R.id.toolbar, R.string.title_subreddit_settings, true, true); diff --git a/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsViewType.java b/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsViewType.java index 252a2566b..ee6af29a0 100644 --- a/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsViewType.java +++ b/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsViewType.java @@ -1,6 +1,7 @@ package me.edgan.redditslide.ui.settings; import android.os.Bundle; +import android.util.TypedValue; import android.view.MenuItem; import android.view.View; import android.widget.TextView; @@ -18,6 +19,13 @@ public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); applyColorTheme(); setContentView(R.layout.activity_settings_viewtype); + + if (SettingValues.oldSwipeMode) { + TypedValue typedValue = new TypedValue(); + getTheme().resolveAttribute(R.attr.card_background, typedValue, true); + getWindow().getDecorView().setBackgroundColor(typedValue.data); + } + setupAppBar(R.id.toolbar, R.string.settings_view_type, true, true); // View type multi choice From 58bf81a6c77081f8c895300f10cac8d283e6e2f8 Mon Sep 17 00:00:00 2001 From: Nathan Grennan Date: Fri, 23 May 2025 16:10:45 -0700 Subject: [PATCH 69/99] Moved the cascade effect fix to MiscUtil.setupOldSwipeModeBackground and converted the code to use it --- .../edgan/redditslide/Activities/Album.java | 9 +++--- .../redditslide/Activities/AlbumPager.java | 6 ++-- .../redditslide/Activities/Announcement.java | 28 +++++-------------- .../redditslide/Activities/CommentSearch.java | 9 ++---- .../Activities/CommentsScreen.java | 9 ++---- .../Activities/CommentsScreenSingle.java | 5 ++-- .../redditslide/Activities/CreateMulti.java | 9 ++---- .../redditslide/Activities/Crosspost.java | 9 ++---- .../redditslide/Activities/Discover.java | 10 ++----- .../me/edgan/redditslide/Activities/Draw.java | 10 ++----- .../Activities/ForceTouchLink.java | 9 ++---- .../Activities/FullscreenVideo.java | 9 ++---- .../edgan/redditslide/Activities/Gallery.java | 9 ++---- .../edgan/redditslide/Activities/Inbox.java | 10 ++----- .../redditslide/Activities/LiveThread.java | 12 ++------ .../edgan/redditslide/Activities/Loader.java | 10 ++----- .../edgan/redditslide/Activities/Login.java | 9 ++---- .../redditslide/Activities/MediaView.java | 9 ++---- .../redditslide/Activities/ModQueue.java | 9 ++---- .../Activities/MultiredditOverview.java | 11 ++------ .../redditslide/Activities/NewsActivity.java | 8 ++---- .../redditslide/Activities/OpenContent.java | 9 ++---- .../redditslide/Activities/PostReadLater.java | 10 ++----- .../edgan/redditslide/Activities/Profile.java | 9 ++---- .../redditslide/Activities/ReaderMode.java | 13 ++------- .../Activities/Reauthenticate.java | 10 ++----- .../redditslide/Activities/RedditGallery.java | 8 ++---- .../Activities/RedditGalleryPager.java | 9 ++---- .../edgan/redditslide/Activities/Related.java | 9 ++---- .../edgan/redditslide/Activities/Search.java | 9 ++---- .../redditslide/Activities/SendMessage.java | 9 ++---- .../Activities/SettingsGeneralActivity.java | 11 ++------ .../redditslide/Activities/SetupWidget.java | 9 ++---- .../redditslide/Activities/Shadowbox.java | 9 ++---- .../Activities/ShadowboxComments.java | 10 ++----- .../redditslide/Activities/Shortcut.java | 10 ++----- .../edgan/redditslide/Activities/Submit.java | 11 ++------ .../Activities/SubredditSearch.java | 10 ++----- .../redditslide/Activities/SubredditView.java | 8 +----- .../edgan/redditslide/Activities/Tumblr.java | 7 ++--- .../redditslide/Activities/TumblrPager.java | 13 ++------- .../redditslide/Activities/Tutorial.java | 7 ++--- .../edgan/redditslide/Activities/Website.java | 9 ++---- .../me/edgan/redditslide/Activities/Wiki.java | 9 ++---- .../ui/settings/EditCardsLayout.java | 8 ++---- .../ui/settings/ManageOfflineContent.java | 9 ++---- .../ui/settings/SettingsAbout.java | 9 ++---- .../ui/settings/SettingsActivity.java | 8 ++---- .../ui/settings/SettingsComments.java | 9 ++---- .../redditslide/ui/settings/SettingsData.java | 9 ++---- .../ui/settings/SettingsFilter.java | 8 ++---- .../redditslide/ui/settings/SettingsFont.java | 9 ++---- .../ui/settings/SettingsGeneral.java | 8 ++---- .../ui/settings/SettingsHandling.java | 8 ++---- .../ui/settings/SettingsHistory.java | 9 ++---- .../redditslide/ui/settings/SettingsLibs.java | 9 ++---- .../ui/settings/SettingsModeration.java | 9 ++---- .../ui/settings/SettingsReddit.java | 9 ++---- .../ui/settings/SettingsSubreddit.java | 8 ++---- .../ui/settings/SettingsSynccit.java | 8 ++---- .../ui/settings/SettingsTheme.java | 9 ++---- .../ui/settings/SettingsViewType.java | 8 ++---- .../settings/dragSort/ReorderSubreddits.java | 8 ++---- .../me/edgan/redditslide/util/MiscUtil.java | 11 ++++++++ .../ui/settings/SettingsBackup.java | 11 ++------ .../ui/settings/SettingsBackup.java | 13 ++------- 66 files changed, 154 insertions(+), 470 deletions(-) diff --git a/app/src/main/java/me/edgan/redditslide/Activities/Album.java b/app/src/main/java/me/edgan/redditslide/Activities/Album.java index b46b11611..c8c2c2562 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/Album.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/Album.java @@ -7,7 +7,6 @@ import android.graphics.Color; import android.os.AsyncTask; import android.os.Bundle; -import android.util.TypedValue; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; @@ -37,6 +36,7 @@ import me.edgan.redditslide.util.DialogUtil; import me.edgan.redditslide.util.ImageSaveUtils; import me.edgan.redditslide.util.LinkUtil; +import me.edgan.redditslide.util.MiscUtil; import java.util.ArrayList; import java.util.List; @@ -149,11 +149,10 @@ public void onCreate(Bundle savedInstanceState) { pager.setAdapter(album); pager.setCurrentItem(1); + if (SettingValues.oldSwipeMode) { - // Set an opaque background for the ViewPager - TypedValue typedValue = new TypedValue(); - getTheme().resolveAttribute(R.attr.card_background, typedValue, true); - pager.setBackgroundColor(typedValue.data); + MiscUtil.setupOldSwipeModeBackground(this, pager); + pager.addOnPageChangeListener( new ViewPager.SimpleOnPageChangeListener() { @Override diff --git a/app/src/main/java/me/edgan/redditslide/Activities/AlbumPager.java b/app/src/main/java/me/edgan/redditslide/Activities/AlbumPager.java index bc6e6bc78..49126366a 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/AlbumPager.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/AlbumPager.java @@ -14,7 +14,6 @@ import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; -import android.util.TypedValue; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; @@ -70,6 +69,7 @@ import me.edgan.redditslide.util.ShareUtil; import me.edgan.redditslide.util.StorageUtil; import me.edgan.redditslide.util.SubmissionParser; +import me.edgan.redditslide.util.MiscUtil; import java.util.ArrayList; import java.util.Arrays; @@ -256,9 +256,7 @@ public void run() { if (SettingValues.oldSwipeMode) { startPage = 1; // Set an opaque background for the ViewPager - TypedValue typedValue = new TypedValue(); - getTheme().resolveAttribute(R.attr.card_background, typedValue, true); - p.setBackgroundColor(typedValue.data); + MiscUtil.setupOldSwipeModeBackground(AlbumPager.this, p); } p.setCurrentItem(startPage); diff --git a/app/src/main/java/me/edgan/redditslide/Activities/Announcement.java b/app/src/main/java/me/edgan/redditslide/Activities/Announcement.java index 60f38731f..934e5b6a9 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/Announcement.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/Announcement.java @@ -1,7 +1,6 @@ package me.edgan.redditslide.Activities; import android.os.Bundle; -import android.util.TypedValue; import android.view.View; import android.view.Window; import android.widget.HorizontalScrollView; @@ -11,12 +10,12 @@ import me.edgan.redditslide.OpenRedditLink; import me.edgan.redditslide.R; import me.edgan.redditslide.Reddit; -import me.edgan.redditslide.SettingValues; import me.edgan.redditslide.SpoilerRobotoTextView; import me.edgan.redditslide.Views.CommentOverflow; import me.edgan.redditslide.Views.SidebarLayout; import me.edgan.redditslide.Views.TitleTextView; import me.edgan.redditslide.Visuals.ColorPreferences; +import me.edgan.redditslide.util.MiscUtil; import me.edgan.redditslide.util.SubmissionParser; import java.util.List; @@ -39,21 +38,13 @@ public void onCreate(Bundle savedInstance) { getWindow().setBackgroundDrawableResource(android.R.color.transparent); super.onCreate(savedInstance); setContentView(R.layout.submission_dialog); + MiscUtil.setupOldSwipeModeBackground(this, getWindow().getDecorView()); - if (SettingValues.oldSwipeMode) { - TypedValue typedValue = new TypedValue(); - getTheme().resolveAttribute(R.attr.card_background, typedValue, true); - getWindow().getDecorView().setBackgroundColor(typedValue.data); - } - - SpoilerRobotoTextView spoilerRobotoTextView = - (SpoilerRobotoTextView) findViewById(R.id.submission_dialog_firstTextView); - CommentOverflow commentOverflow = - (CommentOverflow) findViewById(R.id.submission_dialog_commentOverflow); + SpoilerRobotoTextView spoilerRobotoTextView = (SpoilerRobotoTextView) findViewById(R.id.submission_dialog_firstTextView); + CommentOverflow commentOverflow = (CommentOverflow) findViewById(R.id.submission_dialog_commentOverflow); TitleTextView titleTextView = (TitleTextView) findViewById(R.id.submission_dialog_title); AppCompatButton okBtn = (AppCompatButton) findViewById(R.id.submission_dialog_ok); - AppCompatButton commentsBtn = - (AppCompatButton) findViewById(R.id.submission_dialog_comments); + AppCompatButton commentsBtn = (AppCompatButton) findViewById(R.id.submission_dialog_comments); setViews( Reddit.appRestart.getString("page", ""), @@ -66,17 +57,12 @@ public void onCreate(Bundle savedInstance) { commentsBtn.setOnClickListener( v -> { - OpenRedditLink.openUrl( - Announcement.this, Reddit.appRestart.getString("url", ""), true); + OpenRedditLink.openUrl(Announcement.this, Reddit.appRestart.getString("url", ""), true); finish(); }); } - private void setViews( - String rawHTML, - String subredditName, - SpoilerRobotoTextView firstTextView, - CommentOverflow commentOverflow) { + private void setViews(String rawHTML, String subredditName, SpoilerRobotoTextView firstTextView, CommentOverflow commentOverflow) { if (rawHTML.isEmpty()) { return; } diff --git a/app/src/main/java/me/edgan/redditslide/Activities/CommentSearch.java b/app/src/main/java/me/edgan/redditslide/Activities/CommentSearch.java index 9f9d87dec..befe4820d 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/CommentSearch.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/CommentSearch.java @@ -2,7 +2,6 @@ import android.os.Bundle; import android.text.Editable; -import android.util.TypedValue; import android.widget.EditText; import androidx.recyclerview.widget.RecyclerView; @@ -12,8 +11,8 @@ import me.edgan.redditslide.Adapters.CommentObject; import me.edgan.redditslide.DataShare; import me.edgan.redditslide.R; -import me.edgan.redditslide.SettingValues; import me.edgan.redditslide.Views.PreCachingLayoutManager; +import me.edgan.redditslide.util.MiscUtil; import me.edgan.redditslide.util.stubs.SimpleTextWatcher; import net.dean.jraw.models.CommentNode; @@ -36,12 +35,8 @@ public void onCreate(Bundle savedInstance) { super.onCreate(savedInstance); applyColorTheme(); setContentView(R.layout.activity_filtercomments); + MiscUtil.setupOldSwipeModeBackground(this, getWindow().getDecorView()); - if (SettingValues.oldSwipeMode) { - TypedValue typedValue = new TypedValue(); - getTheme().resolveAttribute(R.attr.card_background, typedValue, true); - getWindow().getDecorView().setBackgroundColor(typedValue.data); - } final EditText search = (EditText) findViewById(R.id.search); RecyclerView rv = (RecyclerView) findViewById(R.id.vertical_content); diff --git a/app/src/main/java/me/edgan/redditslide/Activities/CommentsScreen.java b/app/src/main/java/me/edgan/redditslide/Activities/CommentsScreen.java index 3a79e457b..3fd9d3e1d 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/CommentsScreen.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/CommentsScreen.java @@ -30,6 +30,7 @@ import me.edgan.redditslide.SettingValues; import me.edgan.redditslide.util.CustomViewPager; import me.edgan.redditslide.util.KeyboardUtil; +import me.edgan.redditslide.util.MiscUtil; import net.dean.jraw.models.Submission; @@ -37,8 +38,6 @@ import java.util.List; import java.util.Locale; -import android.util.TypedValue; - /** * This activity is responsible for the view when clicking on a post, showing the post and its * comments underneath with the slide left/right for the next post. @@ -220,10 +219,8 @@ public void onCreate(Bundle savedInstance) { pager.setEntryPageIndex(firstPage); if (SettingValues.oldSwipeMode) { - // Set an opaque background for the ViewPager - TypedValue typedValue = new TypedValue(); - getTheme().resolveAttribute(R.attr.card_background, typedValue, true); - pager.setBackgroundColor(typedValue.data); + MiscUtil.setupOldSwipeModeBackground(this, pager); + pager.addOnPageChangeListener(new CommonPageChangeListener() { @Override public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { diff --git a/app/src/main/java/me/edgan/redditslide/Activities/CommentsScreenSingle.java b/app/src/main/java/me/edgan/redditslide/Activities/CommentsScreenSingle.java index a217742ef..c710f07b0 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/CommentsScreenSingle.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/CommentsScreenSingle.java @@ -33,6 +33,7 @@ import me.edgan.redditslide.SwipeLayout.Utils; import me.edgan.redditslide.UserSubscriptions; import me.edgan.redditslide.util.LogUtil; +import me.edgan.redditslide.util.MiscUtil; import net.dean.jraw.models.Submission; @@ -188,9 +189,7 @@ private void setupAdapter() { pager.setCurrentItem(1); if (SettingValues.oldSwipeMode) { - TypedValue typedValue = new TypedValue(); - getTheme().resolveAttribute(R.attr.card_background, typedValue, true); - pager.setBackgroundColor(typedValue.data); + MiscUtil.setupOldSwipeModeBackground(this, pager); pager.addOnPageChangeListener(new CommonPageChangeListener() { @Override diff --git a/app/src/main/java/me/edgan/redditslide/Activities/CreateMulti.java b/app/src/main/java/me/edgan/redditslide/Activities/CreateMulti.java index ec392dac0..240749c8f 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/CreateMulti.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/CreateMulti.java @@ -20,7 +20,6 @@ import android.os.AsyncTask; import android.os.Bundle; import android.util.Log; -import android.util.TypedValue; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; @@ -41,11 +40,11 @@ import me.edgan.redditslide.Authentication; import me.edgan.redditslide.R; -import me.edgan.redditslide.SettingValues; import me.edgan.redditslide.UserSubscriptions; import me.edgan.redditslide.Visuals.Palette; import me.edgan.redditslide.util.BlendModeUtil; import me.edgan.redditslide.util.LogUtil; +import me.edgan.redditslide.util.MiscUtil; import net.dean.jraw.ApiException; import net.dean.jraw.http.MultiRedditUpdateRequest; @@ -84,11 +83,7 @@ protected void onCreate(Bundle savedInstanceState) { applyColorTheme(); setContentView(R.layout.activity_createmulti); - if (SettingValues.oldSwipeMode) { - TypedValue typedValue = new TypedValue(); - getTheme().resolveAttribute(R.attr.card_background, typedValue, true); - getWindow().getDecorView().setBackgroundColor(typedValue.data); - } + MiscUtil.setupOldSwipeModeBackground(this, getWindow().getDecorView()); setupAppBar(R.id.toolbar, "", true, true); diff --git a/app/src/main/java/me/edgan/redditslide/Activities/Crosspost.java b/app/src/main/java/me/edgan/redditslide/Activities/Crosspost.java index 374169f41..ccdc9c3dd 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/Crosspost.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/Crosspost.java @@ -3,7 +3,6 @@ import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; -import android.util.TypedValue; import android.view.View; import android.view.Window; import android.view.WindowManager; @@ -19,12 +18,12 @@ import me.edgan.redditslide.Authentication; import me.edgan.redditslide.OpenRedditLink; import me.edgan.redditslide.R; -import me.edgan.redditslide.SettingValues; import me.edgan.redditslide.SpoilerRobotoTextView; import me.edgan.redditslide.UserSubscriptions; import me.edgan.redditslide.Views.CommentOverflow; import me.edgan.redditslide.util.SubmissionParser; import me.edgan.redditslide.util.stubs.SimpleTextWatcher; +import me.edgan.redditslide.util.MiscUtil; import net.dean.jraw.ApiException; import net.dean.jraw.managers.AccountManager; @@ -47,11 +46,7 @@ public void onCreate(Bundle savedInstanceState) { applyColorTheme(); setContentView(R.layout.activity_crosspost); - if (SettingValues.oldSwipeMode) { - TypedValue typedValue = new TypedValue(); - getTheme().resolveAttribute(R.attr.card_background, typedValue, true); - getWindow().getDecorView().setBackgroundColor(typedValue.data); - } + MiscUtil.setupOldSwipeModeBackground(this, getWindow().getDecorView()); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { Window window = this.getWindow(); diff --git a/app/src/main/java/me/edgan/redditslide/Activities/Discover.java b/app/src/main/java/me/edgan/redditslide/Activities/Discover.java index 2bd905739..1ef01c2bf 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/Discover.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/Discover.java @@ -3,7 +3,6 @@ import android.content.Intent; import android.os.Bundle; import android.text.InputType; -import android.util.TypedValue; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; @@ -22,9 +21,9 @@ import me.edgan.redditslide.Fragments.SubredditListView; import me.edgan.redditslide.R; -import me.edgan.redditslide.SettingValues; import me.edgan.redditslide.Visuals.ColorPreferences; import me.edgan.redditslide.Visuals.Palette; +import me.edgan.redditslide.util.MiscUtil; /** Created by ccrama on 9/17/2015. */ public class Discover extends BaseActivityAnim { @@ -94,12 +93,7 @@ public void onCreate(Bundle savedInstance) { applyColorTheme(""); setContentView(R.layout.activity_multireddits); - - if (SettingValues.oldSwipeMode) { - TypedValue typedValue = new TypedValue(); - getTheme().resolveAttribute(R.attr.card_background, typedValue, true); - getWindow().getDecorView().setBackgroundColor(typedValue.data); - } + MiscUtil.setupOldSwipeModeBackground(this, getWindow().getDecorView()); ((DrawerLayout) findViewById(R.id.drawer_layout)) .setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED); diff --git a/app/src/main/java/me/edgan/redditslide/Activities/Draw.java b/app/src/main/java/me/edgan/redditslide/Activities/Draw.java index ee56d3694..800b3d377 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/Draw.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/Draw.java @@ -5,7 +5,6 @@ import android.graphics.Color; import android.net.Uri; import android.os.Bundle; -import android.util.TypedValue; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; @@ -24,12 +23,12 @@ import me.edgan.redditslide.R; import me.edgan.redditslide.Reddit; -import me.edgan.redditslide.SettingValues; import me.edgan.redditslide.Views.CanvasView; import me.edgan.redditslide.Views.DoEditorActions; import me.edgan.redditslide.Visuals.Palette; import me.edgan.redditslide.util.BlendModeUtil; import me.edgan.redditslide.util.FileUtil; +import me.edgan.redditslide.util.MiscUtil; import java.io.File; import java.io.FileOutputStream; @@ -54,12 +53,7 @@ public void onCreate(Bundle savedInstance) { super.onCreate(savedInstance); applyColorTheme(""); setContentView(R.layout.activity_draw); - - if (SettingValues.oldSwipeMode) { - TypedValue typedValue = new TypedValue(); - getTheme().resolveAttribute(R.attr.card_background, typedValue, true); - getWindow().getDecorView().setBackgroundColor(typedValue.data); - } + MiscUtil.setupOldSwipeModeBackground(this, getWindow().getDecorView()); drawView = (CanvasView) findViewById(R.id.paintView); drawView.setBaseColor(Color.parseColor("#303030")); diff --git a/app/src/main/java/me/edgan/redditslide/Activities/ForceTouchLink.java b/app/src/main/java/me/edgan/redditslide/Activities/ForceTouchLink.java index 84ad634ea..3c036d1de 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/ForceTouchLink.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/ForceTouchLink.java @@ -1,7 +1,6 @@ package me.edgan.redditslide.Activities; import android.os.Bundle; -import android.util.TypedValue; import android.view.MotionEvent; import android.view.View; import android.view.Window; @@ -10,9 +9,9 @@ import me.edgan.redditslide.ContentType; import me.edgan.redditslide.R; import me.edgan.redditslide.Reddit; -import me.edgan.redditslide.SettingValues; import me.edgan.redditslide.Views.ExoVideoView; import me.edgan.redditslide.util.GifUtils; +import me.edgan.redditslide.util.MiscUtil; /** * Created by ccrama on 01/29/2016. @@ -32,11 +31,7 @@ public void onCreate(Bundle savedInstance) { setContentView(R.layout.activity_force_touch_content); - if (SettingValues.oldSwipeMode) { - TypedValue typedValue = new TypedValue(); - getTheme().resolveAttribute(R.attr.card_background, typedValue, true); - getWindow().getDecorView().setBackgroundColor(typedValue.data); - } + MiscUtil.setupOldSwipeModeBackground(this, getWindow().getDecorView()); findViewById(android.R.id.content) .setOnTouchListener( diff --git a/app/src/main/java/me/edgan/redditslide/Activities/FullscreenVideo.java b/app/src/main/java/me/edgan/redditslide/Activities/FullscreenVideo.java index bbae2359d..e1d6c91fa 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/FullscreenVideo.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/FullscreenVideo.java @@ -3,7 +3,6 @@ import android.graphics.Color; import android.os.Build; import android.os.Bundle; -import android.util.TypedValue; import android.view.Window; import android.view.WindowManager; import android.webkit.WebChromeClient; @@ -14,9 +13,9 @@ import me.edgan.redditslide.R; import me.edgan.redditslide.Reddit; -import me.edgan.redditslide.SettingValues; import me.edgan.redditslide.util.LinkUtil; import me.edgan.redditslide.util.LogUtil; +import me.edgan.redditslide.util.MiscUtil; /** Created by ccrama on 3/5/2015. */ public class FullscreenVideo extends FullScreenActivity { @@ -37,11 +36,7 @@ public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_video); - if (SettingValues.oldSwipeMode) { - TypedValue typedValue = new TypedValue(); - getTheme().resolveAttribute(R.attr.card_background, typedValue, true); - getWindow().getDecorView().setBackgroundColor(typedValue.data); - } + MiscUtil.setupOldSwipeModeBackground(this, getWindow().getDecorView()); String data = getIntent().getExtras().getString(EXTRA_HTML); v = (WebView) findViewById(R.id.webgif); diff --git a/app/src/main/java/me/edgan/redditslide/Activities/Gallery.java b/app/src/main/java/me/edgan/redditslide/Activities/Gallery.java index 173e873e0..07727afd7 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/Gallery.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/Gallery.java @@ -2,7 +2,6 @@ import android.content.res.Configuration; import android.os.Bundle; -import android.util.TypedValue; import android.view.View; import androidx.annotation.NonNull; @@ -17,9 +16,9 @@ import me.edgan.redditslide.OfflineSubreddit; import me.edgan.redditslide.PostLoader; import me.edgan.redditslide.R; -import me.edgan.redditslide.SettingValues; import me.edgan.redditslide.Views.CatchStaggeredGridLayoutManager; import me.edgan.redditslide.util.LayoutUtils; +import me.edgan.redditslide.util.MiscUtil; import net.dean.jraw.models.Submission; @@ -54,11 +53,7 @@ public void onCreate(Bundle savedInstance) { super.onCreate(savedInstance); setContentView(R.layout.gallery); - if (SettingValues.oldSwipeMode) { - TypedValue typedValue = new TypedValue(); - getTheme().resolveAttribute(R.attr.card_background, typedValue, true); - getWindow().getDecorView().setBackgroundColor(typedValue.data); - } + MiscUtil.setupOldSwipeModeBackground(this, getWindow().getDecorView()); getWindow() .getDecorView() diff --git a/app/src/main/java/me/edgan/redditslide/Activities/Inbox.java b/app/src/main/java/me/edgan/redditslide/Activities/Inbox.java index 7ec927c2f..5edeff628 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/Inbox.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/Inbox.java @@ -3,7 +3,6 @@ import android.content.Intent; import android.os.AsyncTask; import android.os.Bundle; -import android.util.TypedValue; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; @@ -34,6 +33,7 @@ import me.edgan.redditslide.util.KeyboardUtil; import me.edgan.redditslide.util.LayoutUtils; import me.edgan.redditslide.util.LogUtil; +import me.edgan.redditslide.util.MiscUtil; import net.dean.jraw.managers.InboxManager; @@ -185,13 +185,7 @@ protected Void doInBackground(Void... params) { SettingValues.prefs.edit().putLong("lastInbox", System.currentTimeMillis()).apply(); applyColorTheme(""); setContentView(R.layout.activity_inbox); - - if (SettingValues.oldSwipeMode) { - TypedValue typedValue = new TypedValue(); - getTheme().resolveAttribute(R.attr.card_background, typedValue, true); - getWindow().getDecorView().setBackgroundColor(typedValue.data); - } - + MiscUtil.setupOldSwipeModeBackground(this, getWindow().getDecorView()); setupAppBar(R.id.toolbar, R.string.title_inbox, true, true); mToolbar.setPopupTheme(new ColorPreferences(this).getFontStyle().getBaseId()); diff --git a/app/src/main/java/me/edgan/redditslide/Activities/LiveThread.java b/app/src/main/java/me/edgan/redditslide/Activities/LiveThread.java index c62dac439..85c806854 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/LiveThread.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/LiveThread.java @@ -6,7 +6,6 @@ import android.graphics.drawable.ColorDrawable; import android.os.AsyncTask; import android.os.Bundle; -import android.util.TypedValue; import android.view.Gravity; import android.view.LayoutInflater; import android.view.Menu; @@ -40,7 +39,6 @@ import me.edgan.redditslide.ContentType; import me.edgan.redditslide.R; import me.edgan.redditslide.Reddit; -import me.edgan.redditslide.SettingValues; import me.edgan.redditslide.SpoilerRobotoTextView; import me.edgan.redditslide.Views.CommentOverflow; import me.edgan.redditslide.Views.SidebarLayout; @@ -49,6 +47,7 @@ import me.edgan.redditslide.util.HttpUtil; import me.edgan.redditslide.util.LinkUtil; import me.edgan.redditslide.util.LogUtil; +import me.edgan.redditslide.util.MiscUtil; import me.edgan.redditslide.util.SubmissionParser; import me.edgan.redditslide.util.TimeUtils; import me.edgan.redditslide.util.TwitterObject; @@ -106,16 +105,9 @@ public void onCreate(Bundle savedInstanceState) { getWindow().setBackgroundDrawable(new ColorDrawable(Color.TRANSPARENT)); getWindow().getDecorView().setBackground(null); super.onCreate(savedInstanceState); - applyColorTheme(); - setContentView(R.layout.activity_livethread); - - if (SettingValues.oldSwipeMode) { - TypedValue typedValue = new TypedValue(); - getTheme().resolveAttribute(R.attr.card_background, typedValue, true); - getWindow().getDecorView().setBackgroundColor(typedValue.data); - } + MiscUtil.setupOldSwipeModeBackground(this, getWindow().getDecorView()); baseRecycler = (RecyclerView) findViewById(R.id.content_view); baseRecycler.setLayoutManager(new LinearLayoutManager(LiveThread.this)); diff --git a/app/src/main/java/me/edgan/redditslide/Activities/Loader.java b/app/src/main/java/me/edgan/redditslide/Activities/Loader.java index 97ad6dcac..c6567327d 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/Loader.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/Loader.java @@ -2,10 +2,9 @@ /** Created by carlo_000 on 1/20/2016. */ import android.os.Bundle; -import android.util.TypedValue; import me.edgan.redditslide.R; -import me.edgan.redditslide.SettingValues; +import me.edgan.redditslide.util.MiscUtil; /** Created by ccrama on 9/17/2015. */ public class Loader extends BaseActivity { @@ -16,12 +15,7 @@ public void onCreate(Bundle savedInstance) { super.onCreate(savedInstance); applyColorTheme(); setContentView(R.layout.activity_loading); - - if (SettingValues.oldSwipeMode) { - TypedValue typedValue = new TypedValue(); - getTheme().resolveAttribute(R.attr.card_background, typedValue, true); - getWindow().getDecorView().setBackgroundColor(typedValue.data); - } + MiscUtil.setupOldSwipeModeBackground(this, getWindow().getDecorView()); MainActivity.loader = this; } diff --git a/app/src/main/java/me/edgan/redditslide/Activities/Login.java b/app/src/main/java/me/edgan/redditslide/Activities/Login.java index 97194d60f..2090d153b 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/Login.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/Login.java @@ -8,7 +8,6 @@ import android.os.Build; import android.os.Bundle; import android.util.Log; -import android.util.TypedValue; import android.view.View; import android.webkit.CookieManager; import android.webkit.CookieSyncManager; @@ -25,11 +24,11 @@ import me.edgan.redditslide.Constants; import me.edgan.redditslide.R; import me.edgan.redditslide.Reddit; -import me.edgan.redditslide.SettingValues; import me.edgan.redditslide.UserSubscriptions; import me.edgan.redditslide.Visuals.GetClosestColor; import me.edgan.redditslide.Visuals.Palette; import me.edgan.redditslide.util.LogUtil; +import me.edgan.redditslide.util.MiscUtil; import net.dean.jraw.http.NetworkException; import net.dean.jraw.http.oauth.Credentials; @@ -64,11 +63,7 @@ public void onCreate(Bundle savedInstance) { return; } - if (SettingValues.oldSwipeMode) { - TypedValue typedValue = new TypedValue(); - getTheme().resolveAttribute(R.attr.card_background, typedValue, true); - getWindow().getDecorView().setBackgroundColor(typedValue.data); - } + MiscUtil.setupOldSwipeModeBackground(this, getWindow().getDecorView()); setupAppBar(R.id.toolbar, R.string.title_login, true, true); diff --git a/app/src/main/java/me/edgan/redditslide/Activities/MediaView.java b/app/src/main/java/me/edgan/redditslide/Activities/MediaView.java index 1a8fe9855..7e308f983 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/MediaView.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/MediaView.java @@ -17,7 +17,6 @@ import android.os.Bundle; import android.os.Handler; import android.util.Log; -import android.util.TypedValue; import android.view.View; import android.view.WindowManager; import android.widget.ImageView; @@ -65,6 +64,7 @@ import me.edgan.redditslide.util.NetworkUtil; import me.edgan.redditslide.util.ShareUtil; import me.edgan.redditslide.util.StorageUtil; +import me.edgan.redditslide.util.MiscUtil; import org.apache.commons.text.StringEscapeUtils; @@ -445,12 +445,7 @@ public void onCreate(Bundle savedInstanceState) { } setContentView(R.layout.activity_media); - - if (SettingValues.oldSwipeMode) { - TypedValue typedValue = new TypedValue(); - getTheme().resolveAttribute(R.attr.card_background, typedValue, true); - getWindow().getDecorView().setBackgroundColor(typedValue.data); - } + MiscUtil.setupOldSwipeModeBackground(this, getWindow().getDecorView()); // Keep the screen on getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); diff --git a/app/src/main/java/me/edgan/redditslide/Activities/ModQueue.java b/app/src/main/java/me/edgan/redditslide/Activities/ModQueue.java index 1a9b0bfd5..59abb7c2f 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/ModQueue.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/ModQueue.java @@ -1,7 +1,6 @@ package me.edgan.redditslide.Activities; import android.os.Bundle; -import android.util.TypedValue; import android.view.View; import android.view.ViewGroup; import android.view.animation.LinearInterpolator; @@ -18,10 +17,10 @@ import me.edgan.redditslide.Fragments.ModLog; import me.edgan.redditslide.Fragments.ModPage; import me.edgan.redditslide.R; -import me.edgan.redditslide.SettingValues; import me.edgan.redditslide.UserSubscriptions; import me.edgan.redditslide.Visuals.ColorPreferences; import me.edgan.redditslide.Visuals.Palette; +import me.edgan.redditslide.util.MiscUtil; /** Created by ccrama on 9/17/2015. */ public class ModQueue extends BaseActivityAnim { @@ -37,11 +36,7 @@ public void onCreate(Bundle savedInstance) { applyColorTheme(""); setContentView(R.layout.activity_inbox); - if (SettingValues.oldSwipeMode) { - TypedValue typedValue = new TypedValue(); - getTheme().resolveAttribute(R.attr.card_background, typedValue, true); - getWindow().getDecorView().setBackgroundColor(typedValue.data); - } + MiscUtil.setupOldSwipeModeBackground(this, getWindow().getDecorView()); setupAppBar(R.id.toolbar, R.string.drawer_moderation, true, true); diff --git a/app/src/main/java/me/edgan/redditslide/Activities/MultiredditOverview.java b/app/src/main/java/me/edgan/redditslide/Activities/MultiredditOverview.java index 409e1e96c..480f35de5 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/MultiredditOverview.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/MultiredditOverview.java @@ -8,7 +8,6 @@ import android.os.Bundle; import android.text.Spannable; import android.util.Log; -import android.util.TypedValue; import android.view.Gravity; import android.view.KeyEvent; import android.view.Menu; @@ -47,6 +46,7 @@ import me.edgan.redditslide.Visuals.Palette; import me.edgan.redditslide.util.BlendModeUtil; import me.edgan.redditslide.util.LogUtil; +import me.edgan.redditslide.util.MiscUtil; import me.edgan.redditslide.util.SortingUtil; import net.dean.jraw.models.MultiReddit; @@ -329,19 +329,12 @@ private void buildDialog(boolean wasException) { @Override public void onCreate(Bundle savedInstance) { overrideSwipeFromAnywhere(); - multiActivity = this; - super.onCreate(savedInstance); applyColorTheme(""); setContentView(R.layout.activity_multireddits); - - if (SettingValues.oldSwipeMode) { - TypedValue typedValue = new TypedValue(); - getTheme().resolveAttribute(R.attr.card_background, typedValue, true); - getWindow().getDecorView().setBackgroundColor(typedValue.data); - } + MiscUtil.setupOldSwipeModeBackground(this, getWindow().getDecorView()); setupAppBar(R.id.toolbar, R.string.title_multireddits, true, false); diff --git a/app/src/main/java/me/edgan/redditslide/Activities/NewsActivity.java b/app/src/main/java/me/edgan/redditslide/Activities/NewsActivity.java index 43a9d23d4..3be310244 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/NewsActivity.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/NewsActivity.java @@ -11,7 +11,6 @@ import android.os.Build; import android.os.Bundle; import android.os.Parcelable; -import android.util.TypedValue; import android.view.View; import android.view.ViewGroup; import android.view.Window; @@ -46,6 +45,7 @@ import me.edgan.redditslide.util.NetworkStateReceiver; import me.edgan.redditslide.util.NetworkUtil; import me.edgan.redditslide.util.StringUtil; +import me.edgan.redditslide.util.MiscUtil; import net.dean.jraw.managers.AccountManager; @@ -144,11 +144,7 @@ protected void onCreate(final Bundle savedInstanceState) { setContentView(R.layout.activity_news); - if (SettingValues.oldSwipeMode) { - TypedValue typedValue = new TypedValue(); - getTheme().resolveAttribute(R.attr.card_background, typedValue, true); - getWindow().getDecorView().setBackgroundColor(typedValue.data); - } + MiscUtil.setupOldSwipeModeBackground(this, getWindow().getDecorView()); mToolbar = (Toolbar) findViewById(R.id.toolbar); mToolbar.setPopupTheme(new ColorPreferences(this).getFontStyle().getBaseId()); diff --git a/app/src/main/java/me/edgan/redditslide/Activities/OpenContent.java b/app/src/main/java/me/edgan/redditslide/Activities/OpenContent.java index 14df90f6c..47ca63255 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/OpenContent.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/OpenContent.java @@ -6,13 +6,12 @@ import android.os.Build; import android.os.Bundle; import android.util.Log; -import android.util.TypedValue; import android.widget.Toast; import me.edgan.redditslide.OpenRedditLink; import me.edgan.redditslide.R; -import me.edgan.redditslide.SettingValues; import me.edgan.redditslide.util.LogUtil; +import me.edgan.redditslide.util.MiscUtil; import java.util.Locale; @@ -26,11 +25,7 @@ public void onCreate(Bundle savedInstance) { super.onCreate(savedInstance); setContentView(R.layout.clear); - if (SettingValues.oldSwipeMode) { - TypedValue typedValue = new TypedValue(); - getTheme().resolveAttribute(R.attr.card_background, typedValue, true); - getWindow().getDecorView().setBackgroundColor(typedValue.data); - } + MiscUtil.setupOldSwipeModeBackground(this, getWindow().getDecorView()); Intent intent = getIntent(); Uri data = intent.getData(); diff --git a/app/src/main/java/me/edgan/redditslide/Activities/PostReadLater.java b/app/src/main/java/me/edgan/redditslide/Activities/PostReadLater.java index b5bff0a9f..d18c24939 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/PostReadLater.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/PostReadLater.java @@ -1,7 +1,6 @@ package me.edgan.redditslide.Activities; import android.os.Bundle; -import android.util.TypedValue; import androidx.annotation.NonNull; import androidx.fragment.app.Fragment; @@ -9,11 +8,10 @@ import androidx.fragment.app.FragmentStatePagerAdapter; import androidx.viewpager.widget.ViewPager; -import me.edgan.redditslide.SettingValues; import me.edgan.redditslide.Fragments.ReadLaterView; import me.edgan.redditslide.R; -import me.edgan.redditslide.SettingValues; import me.edgan.redditslide.Visuals.ColorPreferences; +import me.edgan.redditslide.util.MiscUtil; /** Created by ccrama on 9/17/2015. */ public class PostReadLater extends BaseActivityAnim { @@ -27,11 +25,7 @@ public void onCreate(Bundle savedInstance) { applyColorTheme(); setContentView(R.layout.activity_read_later); - if (SettingValues.oldSwipeMode) { - TypedValue typedValue = new TypedValue(); - getTheme().resolveAttribute(R.attr.card_background, typedValue, true); - getWindow().getDecorView().setBackgroundColor(typedValue.data); - } + MiscUtil.setupOldSwipeModeBackground(this, getWindow().getDecorView()); setupAppBar(R.id.toolbar, this.getString(R.string.read_later), true, true); mToolbar.setPopupTheme(new ColorPreferences(this).getFontStyle().getBaseId()); diff --git a/app/src/main/java/me/edgan/redditslide/Activities/Profile.java b/app/src/main/java/me/edgan/redditslide/Activities/Profile.java index 2ba301d9f..c727e30d7 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/Profile.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/Profile.java @@ -9,7 +9,6 @@ import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; -import android.util.TypedValue; import android.text.Spannable; import android.text.SpannableStringBuilder; import android.text.Spanned; @@ -59,6 +58,7 @@ import me.edgan.redditslide.util.LogUtil; import me.edgan.redditslide.util.SortingUtil; import me.edgan.redditslide.util.TimeUtils; +import me.edgan.redditslide.util.MiscUtil; import net.dean.jraw.fluent.FluentRedditClient; import net.dean.jraw.http.RestResponse; @@ -119,12 +119,7 @@ public void onCreate(Bundle savedInstance) { applyColorTheme(); setContentView(R.layout.activity_profile); - - if (SettingValues.oldSwipeMode) { - TypedValue typedValue = new TypedValue(); - getTheme().resolveAttribute(R.attr.card_background, typedValue, true); - getWindow().getDecorView().setBackgroundColor(typedValue.data); - } + MiscUtil.setupOldSwipeModeBackground(this, getWindow().getDecorView()); setupUserAppBar(R.id.toolbar, name, true, name); mToolbar.setPopupTheme(new ColorPreferences(this).getFontStyle().getBaseId()); diff --git a/app/src/main/java/me/edgan/redditslide/Activities/ReaderMode.java b/app/src/main/java/me/edgan/redditslide/Activities/ReaderMode.java index f16228476..5bad3f4a8 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/ReaderMode.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/ReaderMode.java @@ -3,7 +3,6 @@ import android.content.Intent; import android.os.AsyncTask; import android.os.Bundle; -import android.util.TypedValue; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; @@ -17,10 +16,10 @@ import me.edgan.redditslide.Constants; import me.edgan.redditslide.R; import me.edgan.redditslide.Reddit; -import me.edgan.redditslide.SettingValues; import me.edgan.redditslide.SpoilerRobotoTextView; import me.edgan.redditslide.Visuals.Palette; import me.edgan.redditslide.util.LinkUtil; +import me.edgan.redditslide.util.MiscUtil; import org.apache.commons.text.StringEscapeUtils; import org.jsoup.Connection; @@ -39,15 +38,9 @@ public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); applyColorTheme(""); setContentView(R.layout.activity_reader); + MiscUtil.setupOldSwipeModeBackground(this, getWindow().getDecorView()); - if (SettingValues.oldSwipeMode) { - TypedValue typedValue = new TypedValue(); - getTheme().resolveAttribute(R.attr.card_background, typedValue, true); - getWindow().getDecorView().setBackgroundColor(typedValue.data); - } - - mSubredditColor = - getIntent().getExtras().getInt(LinkUtil.EXTRA_COLOR, Palette.getDefaultColor()); + mSubredditColor = getIntent().getExtras().getInt(LinkUtil.EXTRA_COLOR, Palette.getDefaultColor()); setSupportActionBar((Toolbar) findViewById(R.id.toolbar)); setupAppBar(R.id.toolbar, "", true, mSubredditColor, R.id.appbar); diff --git a/app/src/main/java/me/edgan/redditslide/Activities/Reauthenticate.java b/app/src/main/java/me/edgan/redditslide/Activities/Reauthenticate.java index f18065644..204abd6be 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/Reauthenticate.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/Reauthenticate.java @@ -6,7 +6,6 @@ import android.os.Build; import android.os.Bundle; import android.util.Log; -import android.util.TypedValue; import android.view.View; import android.webkit.CookieManager; import android.webkit.WebChromeClient; @@ -21,8 +20,8 @@ import me.edgan.redditslide.Constants; import me.edgan.redditslide.R; import me.edgan.redditslide.Reddit; -import me.edgan.redditslide.SettingValues; import me.edgan.redditslide.util.LogUtil; +import me.edgan.redditslide.util.MiscUtil; import net.dean.jraw.http.NetworkException; import net.dean.jraw.http.oauth.Credentials; @@ -41,12 +40,7 @@ public void onCreate(Bundle savedInstance) { super.onCreate(savedInstance); applyColorTheme(""); setContentView(R.layout.activity_login); - - if (SettingValues.oldSwipeMode) { - TypedValue typedValue = new TypedValue(); - getTheme().resolveAttribute(R.attr.card_background, typedValue, true); - getWindow().getDecorView().setBackgroundColor(typedValue.data); - } + MiscUtil.setupOldSwipeModeBackground(this, getWindow().getDecorView()); setupAppBar(R.id.toolbar, "Re-authenticate", true, true); diff --git a/app/src/main/java/me/edgan/redditslide/Activities/RedditGallery.java b/app/src/main/java/me/edgan/redditslide/Activities/RedditGallery.java index 975135ac2..73776b34b 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/RedditGallery.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/RedditGallery.java @@ -6,7 +6,6 @@ import android.graphics.Color; import android.net.Uri; import android.os.Bundle; -import android.util.TypedValue; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; @@ -38,6 +37,7 @@ import me.edgan.redditslide.util.ImageSaveUtils; import me.edgan.redditslide.util.LinkUtil; import me.edgan.redditslide.util.LogUtil; +import me.edgan.redditslide.util.MiscUtil; import java.util.ArrayList; import java.util.List; @@ -185,11 +185,9 @@ public void onCreate(Bundle savedInstanceState) { gallery = new RedditGalleryPagerAdapter(getSupportFragmentManager()); pager.setAdapter(gallery); pager.setCurrentItem(1); + if (SettingValues.oldSwipeMode) { - // Set an opaque background for the ViewPager - TypedValue typedValue = new TypedValue(); - getTheme().resolveAttribute(R.attr.card_background, typedValue, true); - pager.setBackgroundColor(typedValue.data); + MiscUtil.setupOldSwipeModeBackground(this, pager); pager.addOnPageChangeListener( new ViewPager.SimpleOnPageChangeListener() { diff --git a/app/src/main/java/me/edgan/redditslide/Activities/RedditGalleryPager.java b/app/src/main/java/me/edgan/redditslide/Activities/RedditGalleryPager.java index 7a9f8dadd..28ebc8c5e 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/RedditGalleryPager.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/RedditGalleryPager.java @@ -8,7 +8,6 @@ import android.net.Uri; import android.os.Bundle; import android.util.Log; -import android.util.TypedValue; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; @@ -44,6 +43,7 @@ import me.edgan.redditslide.util.LinkUtil; import me.edgan.redditslide.util.NetworkUtil; import me.edgan.redditslide.util.ShareUtil; +import me.edgan.redditslide.util.MiscUtil; import java.util.ArrayList; import java.util.Arrays; @@ -197,12 +197,7 @@ public void onCreate(Bundle savedInstanceState) { GalleryViewPagerAdapter adapter = new GalleryViewPagerAdapter(getSupportFragmentManager()); p.setAdapter(adapter); - if (SettingValues.oldSwipeMode) { - // Set an opaque background for the ViewPager - TypedValue typedValue = new TypedValue(); - getTheme().resolveAttribute(R.attr.card_background, typedValue, true); - p.setBackgroundColor(typedValue.data); - } + MiscUtil.setupOldSwipeModeBackground(this, p); p.post( new Runnable() { diff --git a/app/src/main/java/me/edgan/redditslide/Activities/Related.java b/app/src/main/java/me/edgan/redditslide/Activities/Related.java index 003e3daf0..0e89b8e2e 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/Related.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/Related.java @@ -2,7 +2,6 @@ import android.content.Intent; import android.os.Bundle; -import android.util.TypedValue; import android.view.MenuItem; import androidx.annotation.NonNull; @@ -14,12 +13,12 @@ import me.edgan.redditslide.Adapters.SubredditSearchPosts; import me.edgan.redditslide.Constants; import me.edgan.redditslide.R; -import me.edgan.redditslide.SettingValues; import me.edgan.redditslide.Views.CatchStaggeredGridLayoutManager; import me.edgan.redditslide.Views.PreCachingLayoutManager; import me.edgan.redditslide.Visuals.ColorPreferences; import me.edgan.redditslide.Visuals.Palette; import me.edgan.redditslide.handler.ToolbarScrollHideHandler; +import me.edgan.redditslide.util.MiscUtil; public class Related extends BaseActivityAnim { @@ -51,11 +50,7 @@ public void onCreate(Bundle savedInstanceState) { applyColorTheme(""); setContentView(R.layout.activity_search); - if (SettingValues.oldSwipeMode) { - TypedValue typedValue = new TypedValue(); - getTheme().resolveAttribute(R.attr.card_background, typedValue, true); - getWindow().getDecorView().setBackgroundColor(typedValue.data); - } + MiscUtil.setupOldSwipeModeBackground(this, getWindow().getDecorView()); Intent intent = getIntent(); if (intent.hasExtra(Intent.EXTRA_TEXT) diff --git a/app/src/main/java/me/edgan/redditslide/Activities/Search.java b/app/src/main/java/me/edgan/redditslide/Activities/Search.java index ab0421de9..a4becc3ee 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/Search.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/Search.java @@ -4,7 +4,6 @@ import android.content.Intent; import android.content.res.Configuration; import android.os.Bundle; -import android.util.TypedValue; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; @@ -22,7 +21,6 @@ import me.edgan.redditslide.Adapters.SubredditSearchPosts; import me.edgan.redditslide.Constants; import me.edgan.redditslide.R; -import me.edgan.redditslide.SettingValues; import me.edgan.redditslide.Views.CatchStaggeredGridLayoutManager; import me.edgan.redditslide.Views.PreCachingLayoutManager; import me.edgan.redditslide.Visuals.ColorPreferences; @@ -32,6 +30,7 @@ import me.edgan.redditslide.util.LayoutUtils; import me.edgan.redditslide.util.SortingUtil; import me.edgan.redditslide.util.TimeUtils; +import me.edgan.redditslide.util.MiscUtil; import net.dean.jraw.paginators.SubmissionSearchPaginator; import net.dean.jraw.paginators.TimePeriod; @@ -245,11 +244,7 @@ public void onCreate(Bundle savedInstanceState) { applyColorTheme(""); setContentView(R.layout.activity_search); - if (SettingValues.oldSwipeMode) { - TypedValue typedValue = new TypedValue(); - getTheme().resolveAttribute(R.attr.card_background, typedValue, true); - getWindow().getDecorView().setBackgroundColor(typedValue.data); - } + MiscUtil.setupOldSwipeModeBackground(this, getWindow().getDecorView()); where = getIntent().getExtras().getString(EXTRA_TERM, ""); diff --git a/app/src/main/java/me/edgan/redditslide/Activities/SendMessage.java b/app/src/main/java/me/edgan/redditslide/Activities/SendMessage.java index 69e9c09d6..f947b67d6 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/SendMessage.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/SendMessage.java @@ -4,7 +4,6 @@ import android.os.Build; import android.os.Bundle; import android.text.InputType; -import android.util.TypedValue; import android.view.View; import android.view.Window; import android.view.WindowManager; @@ -21,10 +20,10 @@ import me.edgan.redditslide.Authentication; import me.edgan.redditslide.DataShare; import me.edgan.redditslide.R; -import me.edgan.redditslide.SettingValues; import me.edgan.redditslide.UserSubscriptions; import me.edgan.redditslide.Views.DoEditorActions; import me.edgan.redditslide.Visuals.Palette; +import me.edgan.redditslide.util.MiscUtil; import net.dean.jraw.ApiException; import net.dean.jraw.managers.InboxManager; @@ -63,11 +62,7 @@ public void onCreate(Bundle savedInstanceState) { applyColorTheme(); setContentView(R.layout.activity_sendmessage); - if (SettingValues.oldSwipeMode) { - TypedValue typedValue = new TypedValue(); - getTheme().resolveAttribute(R.attr.card_background, typedValue, true); - getWindow().getDecorView().setBackgroundColor(typedValue.data); - } + MiscUtil.setupOldSwipeModeBackground(this, getWindow().getDecorView()); final Toolbar b = (Toolbar) findViewById(R.id.toolbar); final String name; diff --git a/app/src/main/java/me/edgan/redditslide/Activities/SettingsGeneralActivity.java b/app/src/main/java/me/edgan/redditslide/Activities/SettingsGeneralActivity.java index b86ff2abc..8887b82ad 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/SettingsGeneralActivity.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/SettingsGeneralActivity.java @@ -2,7 +2,6 @@ import android.content.pm.PackageManager; import android.os.Bundle; -import android.util.TypedValue; import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; @@ -10,8 +9,8 @@ import android.widget.Toast; import me.edgan.redditslide.R; -import me.edgan.redditslide.SettingValues; import me.edgan.redditslide.util.LogUtil; +import me.edgan.redditslide.util.MiscUtil; public class SettingsGeneralActivity extends AppCompatActivity { @@ -21,13 +20,7 @@ public class SettingsGeneralActivity extends AppCompatActivity { protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_settings_general); - - if (SettingValues.oldSwipeMode) { - TypedValue typedValue = new TypedValue(); - getTheme().resolveAttribute(R.attr.card_background, typedValue, true); - getWindow().getDecorView().setBackgroundColor(typedValue.data); - } - + MiscUtil.setupOldSwipeModeBackground(this, getWindow().getDecorView()); } @Override diff --git a/app/src/main/java/me/edgan/redditslide/Activities/SetupWidget.java b/app/src/main/java/me/edgan/redditslide/Activities/SetupWidget.java index 517dc1ecf..808c61bcc 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/SetupWidget.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/SetupWidget.java @@ -6,7 +6,6 @@ import android.content.Intent; import android.os.Bundle; import android.text.Editable; -import android.util.TypedValue; import android.view.View; import android.widget.EditText; import android.widget.ListView; @@ -16,13 +15,13 @@ import me.edgan.redditslide.Adapters.SubChooseAdapter; import me.edgan.redditslide.R; -import me.edgan.redditslide.SettingValues; import me.edgan.redditslide.UserSubscriptions; import me.edgan.redditslide.Visuals.ColorPreferences; import me.edgan.redditslide.Visuals.FontPreferences; import me.edgan.redditslide.Widget.SubredditWidgetProvider; import me.edgan.redditslide.util.SortingUtil; import me.edgan.redditslide.util.stubs.SimpleTextWatcher; +import me.edgan.redditslide.util.MiscUtil; import java.util.ArrayList; @@ -63,11 +62,7 @@ public void doShortcut() { setContentView(R.layout.activity_setup_widget); - if (SettingValues.oldSwipeMode) { - TypedValue typedValue = new TypedValue(); - getTheme().resolveAttribute(R.attr.card_background, typedValue, true); - getWindow().getDecorView().setBackgroundColor(typedValue.data); - } + MiscUtil.setupOldSwipeModeBackground(this, getWindow().getDecorView()); setupAppBar(R.id.toolbar, R.string.widget_creation_title, true, true); header = getLayoutInflater().inflate(R.layout.widget_header, null); diff --git a/app/src/main/java/me/edgan/redditslide/Activities/Shadowbox.java b/app/src/main/java/me/edgan/redditslide/Activities/Shadowbox.java index b0d5b6f44..8068fa548 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/Shadowbox.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/Shadowbox.java @@ -1,7 +1,6 @@ package me.edgan.redditslide.Activities; import android.os.Bundle; -import android.util.TypedValue; import androidx.annotation.NonNull; import androidx.fragment.app.Fragment; @@ -26,6 +25,7 @@ import me.edgan.redditslide.PostLoader; import me.edgan.redditslide.R; import me.edgan.redditslide.SettingValues; +import me.edgan.redditslide.util.MiscUtil; import net.dean.jraw.models.Submission; @@ -63,12 +63,7 @@ public void onCreate(Bundle savedInstance) { applyDarkColorTheme(subreddit); super.onCreate(savedInstance); setContentView(R.layout.activity_slide); - - if (SettingValues.oldSwipeMode) { - TypedValue typedValue = new TypedValue(); - getTheme().resolveAttribute(R.attr.card_background, typedValue, true); - getWindow().getDecorView().setBackgroundColor(typedValue.data); - } + MiscUtil.setupOldSwipeModeBackground(this, getWindow().getDecorView()); long offline = getIntent().getLongExtra("offline", 0L); diff --git a/app/src/main/java/me/edgan/redditslide/Activities/ShadowboxComments.java b/app/src/main/java/me/edgan/redditslide/Activities/ShadowboxComments.java index 0dcccdf68..011e2230c 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/ShadowboxComments.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/ShadowboxComments.java @@ -1,7 +1,6 @@ package me.edgan.redditslide.Activities; import android.os.Bundle; -import android.util.TypedValue; import androidx.annotation.NonNull; import androidx.fragment.app.Fragment; @@ -14,7 +13,7 @@ import me.edgan.redditslide.Fragments.AlbumFullComments; import me.edgan.redditslide.Fragments.MediaFragmentComment; import me.edgan.redditslide.R; -import me.edgan.redditslide.SettingValues; +import me.edgan.redditslide.util.MiscUtil; import net.dean.jraw.models.Comment; @@ -34,12 +33,7 @@ public void onCreate(Bundle savedInstance) { applyDarkColorTheme(comments.get(0).comment.getComment().getSubredditName()); super.onCreate(savedInstance); setContentView(R.layout.activity_slide); - - if (SettingValues.oldSwipeMode) { - TypedValue typedValue = new TypedValue(); - getTheme().resolveAttribute(R.attr.card_background, typedValue, true); - getWindow().getDecorView().setBackgroundColor(typedValue.data); - } + MiscUtil.setupOldSwipeModeBackground(this, getWindow().getDecorView()); ViewPager pager = (ViewPager) findViewById(R.id.content_view); commentPager = new ShadowboxCommentsPagerAdapter(getSupportFragmentManager()); diff --git a/app/src/main/java/me/edgan/redditslide/Activities/Shortcut.java b/app/src/main/java/me/edgan/redditslide/Activities/Shortcut.java index 7d94edb5c..8338e02b4 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/Shortcut.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/Shortcut.java @@ -2,17 +2,16 @@ import android.os.Bundle; import android.text.Editable; -import android.util.TypedValue; import android.view.View; import android.widget.EditText; import android.widget.ListView; import me.edgan.redditslide.Adapters.SubChooseAdapter; import me.edgan.redditslide.R; -import me.edgan.redditslide.SettingValues; import me.edgan.redditslide.UserSubscriptions; import me.edgan.redditslide.Visuals.ColorPreferences; import me.edgan.redditslide.Visuals.FontPreferences; +import me.edgan.redditslide.util.MiscUtil; import me.edgan.redditslide.util.stubs.SimpleTextWatcher; import java.util.ArrayList; @@ -35,12 +34,7 @@ protected void onCreate(Bundle savedInstanceState) { public void doShortcut() { setContentView(R.layout.activity_setup_widget); - - if (SettingValues.oldSwipeMode) { - TypedValue typedValue = new TypedValue(); - getTheme().resolveAttribute(R.attr.card_background, typedValue, true); - getWindow().getDecorView().setBackgroundColor(typedValue.data); - } + MiscUtil.setupOldSwipeModeBackground(this, getWindow().getDecorView()); setupAppBar(R.id.toolbar, R.string.shortcut_creation_title, true, true); header = getLayoutInflater().inflate(R.layout.shortcut_header, null); diff --git a/app/src/main/java/me/edgan/redditslide/Activities/Submit.java b/app/src/main/java/me/edgan/redditslide/Activities/Submit.java index 4ef06abf3..18f7da19b 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/Submit.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/Submit.java @@ -7,7 +7,6 @@ import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; -import android.util.TypedValue; import android.view.View; import android.view.Window; import android.view.WindowManager; @@ -42,7 +41,6 @@ import me.edgan.redditslide.OpenRedditLink; import me.edgan.redditslide.R; import me.edgan.redditslide.Reddit; -import me.edgan.redditslide.SettingValues; import me.edgan.redditslide.SpoilerRobotoTextView; import me.edgan.redditslide.UserSubscriptions; import me.edgan.redditslide.Views.CommentOverflow; @@ -51,6 +49,7 @@ import me.edgan.redditslide.util.HttpUtil; import me.edgan.redditslide.util.KeyboardUtil; import me.edgan.redditslide.util.LogUtil; +import me.edgan.redditslide.util.MiscUtil; import me.edgan.redditslide.util.SubmissionParser; import me.edgan.redditslide.util.TitleExtractor; import me.edgan.redditslide.util.stubs.SimpleTextWatcher; @@ -111,18 +110,14 @@ public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); applyColorTheme(); setContentView(R.layout.activity_submit); - - if (SettingValues.oldSwipeMode) { - TypedValue typedValue = new TypedValue(); - getTheme().resolveAttribute(R.attr.card_background, typedValue, true); - getWindow().getDecorView().setBackgroundColor(typedValue.data); - } + MiscUtil.setupOldSwipeModeBackground(this, getWindow().getDecorView()); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { Window window = this.getWindow(); window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); } + setupAppBar(R.id.toolbar, R.string.title_submit_post, true, true); inboxReplies = (SwitchCompat) findViewById(R.id.replies); diff --git a/app/src/main/java/me/edgan/redditslide/Activities/SubredditSearch.java b/app/src/main/java/me/edgan/redditslide/Activities/SubredditSearch.java index 2b9beaa6b..d4c9b70f5 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/SubredditSearch.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/SubredditSearch.java @@ -2,7 +2,6 @@ import android.content.Intent; import android.os.Bundle; -import android.util.TypedValue; import android.text.InputType; import android.view.Menu; import android.view.MenuInflater; @@ -18,7 +17,7 @@ import me.edgan.redditslide.Fragments.SubredditListView; import me.edgan.redditslide.R; -import me.edgan.redditslide.SettingValues; +import me.edgan.redditslide.util.MiscUtil; /** Created by ccrama on 9/17/2015. */ public class SubredditSearch extends BaseActivityAnim { @@ -87,12 +86,7 @@ public void onCreate(Bundle savedInstance) { term = getIntent().getExtras().getString("term"); applyColorTheme(""); setContentView(R.layout.activity_fragmentinner); - - if (SettingValues.oldSwipeMode) { - TypedValue typedValue = new TypedValue(); - getTheme().resolveAttribute(R.attr.card_background, typedValue, true); - getWindow().getDecorView().setBackgroundColor(typedValue.data); - } + MiscUtil.setupOldSwipeModeBackground(this, getWindow().getDecorView()); setupAppBar(R.id.toolbar, term, true, true); diff --git a/app/src/main/java/me/edgan/redditslide/Activities/SubredditView.java b/app/src/main/java/me/edgan/redditslide/Activities/SubredditView.java index 4ee113606..1168d1da2 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/SubredditView.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/SubredditView.java @@ -13,7 +13,6 @@ import android.os.Parcelable; import android.text.Spannable; import android.util.Log; -import android.util.TypedValue; import android.view.Gravity; import android.view.LayoutInflater; import android.view.Menu; @@ -200,12 +199,7 @@ public void onCreate(Bundle savedInstanceState) { pager.setAdapter(adapter); pager.setCurrentItem(1); - if (SettingValues.oldSwipeMode) { - // Set an opaque background for the ViewPager - TypedValue typedValue = new TypedValue(); - getTheme().resolveAttribute(R.attr.card_background, typedValue, true); - pager.setBackgroundColor(typedValue.data); - } + MiscUtil.setupOldSwipeModeBackground(this, pager); mToolbar.setOnClickListener( new View.OnClickListener() { diff --git a/app/src/main/java/me/edgan/redditslide/Activities/Tumblr.java b/app/src/main/java/me/edgan/redditslide/Activities/Tumblr.java index 179a89f48..6a2bcea57 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/Tumblr.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/Tumblr.java @@ -5,7 +5,6 @@ import android.graphics.Color; import android.os.AsyncTask; import android.os.Bundle; -import android.util.TypedValue; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; @@ -34,6 +33,7 @@ import me.edgan.redditslide.util.DialogUtil; import me.edgan.redditslide.util.ImageSaveUtils; import me.edgan.redditslide.util.LinkUtil; +import me.edgan.redditslide.util.MiscUtil; import java.util.ArrayList; import java.util.List; @@ -142,10 +142,7 @@ public void onCreate(Bundle savedInstanceState) { } if (SettingValues.oldSwipeMode) { - // Set an opaque background for the ViewPager - TypedValue typedValue = new TypedValue(); - getTheme().resolveAttribute(R.attr.card_background, typedValue, true); - pager.setBackgroundColor(typedValue.data); + MiscUtil.setupOldSwipeModeBackground(this, pager); pager.addOnPageChangeListener( new ViewPager.SimpleOnPageChangeListener() { diff --git a/app/src/main/java/me/edgan/redditslide/Activities/TumblrPager.java b/app/src/main/java/me/edgan/redditslide/Activities/TumblrPager.java index a83dd222b..1df5d0cc3 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/TumblrPager.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/TumblrPager.java @@ -12,7 +12,6 @@ import android.os.AsyncTask; import android.os.Bundle; import android.util.Log; -import android.util.TypedValue; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; @@ -70,6 +69,7 @@ import me.edgan.redditslide.util.NetworkUtil; import me.edgan.redditslide.util.ShareUtil; import me.edgan.redditslide.util.SubmissionParser; +import me.edgan.redditslide.util.MiscUtil; import java.net.URI; import java.net.URISyntaxException; @@ -211,16 +211,10 @@ public void doWithData(final List jsonElements) { getSupportActionBar().setSubtitle(1 + "/" + images.size()); } - TumblrViewPagerAdapter adapter = - new TumblrViewPagerAdapter(getSupportFragmentManager()); + TumblrViewPagerAdapter adapter = new TumblrViewPagerAdapter(getSupportFragmentManager()); p.setAdapter(adapter); - if (SettingValues.oldSwipeMode) { - // Set an opaque background for the ViewPager - TypedValue typedValue = new TypedValue(); - getTheme().resolveAttribute(R.attr.card_background, typedValue, true); - p.setBackgroundColor(typedValue.data); - } + MiscUtil.setupOldSwipeModeBackground(TumblrPager.this, p); int startPage = 0; @@ -230,7 +224,6 @@ public void doWithData(final List jsonElements) { p.setCurrentItem(startPage); - p.post( new Runnable() { @Override diff --git a/app/src/main/java/me/edgan/redditslide/Activities/Tutorial.java b/app/src/main/java/me/edgan/redditslide/Activities/Tutorial.java index 0a108374e..6dea66e06 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/Tutorial.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/Tutorial.java @@ -59,6 +59,7 @@ import me.edgan.redditslide.util.BlendModeUtil; import me.edgan.redditslide.util.LogUtil; import me.edgan.redditslide.util.QrCodeScannerHelper; +import me.edgan.redditslide.util.MiscUtil; /** Created by ccrama on 3/5/2015. */ public class Tutorial extends AppCompatActivity { @@ -84,11 +85,7 @@ protected void onCreate(Bundle savedInstanceState) { binding = ActivityTutorialBinding.inflate(getLayoutInflater()); setContentView(binding.getRoot()); - if (SettingValues.oldSwipeMode) { - TypedValue typedValue = new TypedValue(); - getTheme().resolveAttribute(R.attr.card_background, typedValue, true); - getWindow().getDecorView().setBackgroundColor(typedValue.data); - } + MiscUtil.setupOldSwipeModeBackground(this, getWindow().getDecorView()); // The pager adapter, which provides the pages to the view pager widget. binding.tutorialViewPager.setAdapter(new TutorialPagerAdapter(getSupportFragmentManager())); diff --git a/app/src/main/java/me/edgan/redditslide/Activities/Website.java b/app/src/main/java/me/edgan/redditslide/Activities/Website.java index 310f161e9..a7d44a51a 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/Website.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/Website.java @@ -4,7 +4,6 @@ import android.net.Uri; import android.os.Build; import android.os.Bundle; -import android.util.TypedValue; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; @@ -36,6 +35,7 @@ import me.edgan.redditslide.util.AdBlocker; import me.edgan.redditslide.util.LinkUtil; import me.edgan.redditslide.util.LogUtil; +import me.edgan.redditslide.util.MiscUtil; import java.net.URI; import java.net.URISyntaxException; @@ -168,12 +168,7 @@ public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); applyColorTheme(""); setContentView(R.layout.activity_web); - - if (SettingValues.oldSwipeMode) { - TypedValue typedValue = new TypedValue(); - getTheme().resolveAttribute(R.attr.card_background, typedValue, true); - getWindow().getDecorView().setBackgroundColor(typedValue.data); - } + MiscUtil.setupOldSwipeModeBackground(this, getWindow().getDecorView()); url = getIntent().getExtras().getString(LinkUtil.EXTRA_URL, ""); subredditColor = diff --git a/app/src/main/java/me/edgan/redditslide/Activities/Wiki.java b/app/src/main/java/me/edgan/redditslide/Activities/Wiki.java index f2be21e79..cf7a8a05a 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/Wiki.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/Wiki.java @@ -4,7 +4,6 @@ import android.graphics.Color; import android.os.AsyncTask; import android.os.Bundle; -import android.util.TypedValue; import androidx.annotation.ColorInt; import androidx.annotation.NonNull; @@ -18,11 +17,11 @@ import me.edgan.redditslide.Authentication; import me.edgan.redditslide.Fragments.WikiPage; import me.edgan.redditslide.R; -import me.edgan.redditslide.SettingValues; import me.edgan.redditslide.Views.ToggleSwipeViewPager; import me.edgan.redditslide.Visuals.ColorPreferences; import me.edgan.redditslide.Visuals.Palette; import me.edgan.redditslide.util.LogUtil; +import me.edgan.redditslide.util.MiscUtil; import net.dean.jraw.managers.WikiManager; @@ -57,11 +56,7 @@ public void onCreate(Bundle savedInstance) { createCustomJavaScript(); setContentView(R.layout.activity_slidetabs); - if (SettingValues.oldSwipeMode) { - TypedValue typedValue = new TypedValue(); - getTheme().resolveAttribute(R.attr.card_background, typedValue, true); - getWindow().getDecorView().setBackgroundColor(typedValue.data); - } + MiscUtil.setupOldSwipeModeBackground(this, getWindow().getDecorView()); setupSubredditAppBar(R.id.toolbar, "/r/" + subreddit + " wiki", true, subreddit); diff --git a/app/src/main/java/me/edgan/redditslide/ui/settings/EditCardsLayout.java b/app/src/main/java/me/edgan/redditslide/ui/settings/EditCardsLayout.java index 40391cb15..64780fc1c 100644 --- a/app/src/main/java/me/edgan/redditslide/ui/settings/EditCardsLayout.java +++ b/app/src/main/java/me/edgan/redditslide/ui/settings/EditCardsLayout.java @@ -2,7 +2,6 @@ import android.content.SharedPreferences; import android.os.Bundle; -import android.util.TypedValue; import android.view.MenuItem; import android.view.View; import android.widget.CompoundButton; @@ -18,6 +17,7 @@ import me.edgan.redditslide.SettingValues; import me.edgan.redditslide.SubmissionCache; import me.edgan.redditslide.Views.CreateCardView; +import me.edgan.redditslide.util.MiscUtil; import java.util.Map; @@ -33,11 +33,7 @@ public void onCreate(Bundle savedInstance) { applyColorTheme(); setContentView(R.layout.activity_settings_theme_card); - if (SettingValues.oldSwipeMode) { - TypedValue typedValue = new TypedValue(); - getTheme().resolveAttribute(R.attr.card_background, typedValue, true); - getWindow().getDecorView().setBackgroundColor(typedValue.data); - } + MiscUtil.setupOldSwipeModeBackground(this, getWindow().getDecorView()); setupAppBar(R.id.toolbar, R.string.settings_layout_default, true, true); diff --git a/app/src/main/java/me/edgan/redditslide/ui/settings/ManageOfflineContent.java b/app/src/main/java/me/edgan/redditslide/ui/settings/ManageOfflineContent.java index edd237a6d..b6f1202bd 100644 --- a/app/src/main/java/me/edgan/redditslide/ui/settings/ManageOfflineContent.java +++ b/app/src/main/java/me/edgan/redditslide/ui/settings/ManageOfflineContent.java @@ -1,12 +1,11 @@ package me.edgan.redditslide.ui.settings; import android.os.Bundle; -import android.util.TypedValue; import android.view.ViewGroup; import me.edgan.redditslide.Activities.BaseActivityAnim; import me.edgan.redditslide.R; -import me.edgan.redditslide.SettingValues; +import me.edgan.redditslide.util.MiscUtil; /** Created by l3d00m on 11/13/2015. */ public class ManageOfflineContent extends BaseActivityAnim { @@ -18,11 +17,7 @@ public void onCreate(Bundle savedInstanceState) { applyColorTheme(); setContentView(R.layout.activity_manage_history); - if (SettingValues.oldSwipeMode) { - TypedValue typedValue = new TypedValue(); - getTheme().resolveAttribute(R.attr.card_background, typedValue, true); - getWindow().getDecorView().setBackgroundColor(typedValue.data); - } + MiscUtil.setupOldSwipeModeBackground(this, getWindow().getDecorView()); setupAppBar(R.id.toolbar, R.string.manage_offline_content, true, true); diff --git a/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsAbout.java b/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsAbout.java index ddce0657e..ea5c3e4f7 100644 --- a/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsAbout.java +++ b/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsAbout.java @@ -4,7 +4,6 @@ import android.content.Intent; import android.content.SharedPreferences; import android.os.Bundle; -import android.util.TypedValue; import android.view.View; import android.widget.TextView; import android.widget.Toast; @@ -13,9 +12,9 @@ import me.edgan.redditslide.BuildConfig; import me.edgan.redditslide.OpenRedditLink; import me.edgan.redditslide.R; -import me.edgan.redditslide.SettingValues; import me.edgan.redditslide.util.ClipboardUtil; import me.edgan.redditslide.util.LinkUtil; +import me.edgan.redditslide.util.MiscUtil; /** Created by l3d00m on 11/12/2015. */ public class SettingsAbout extends BaseActivityAnim { @@ -25,11 +24,7 @@ public void onCreate(Bundle savedInstanceState) { applyColorTheme(); setContentView(R.layout.activity_settings_about); - if (SettingValues.oldSwipeMode) { - TypedValue typedValue = new TypedValue(); - getTheme().resolveAttribute(R.attr.card_background, typedValue, true); - getWindow().getDecorView().setBackgroundColor(typedValue.data); - } + MiscUtil.setupOldSwipeModeBackground(this, getWindow().getDecorView()); setupAppBar(R.id.toolbar, R.string.settings_title_about, true, true); diff --git a/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsActivity.java b/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsActivity.java index f36c8766f..263cccabf 100644 --- a/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsActivity.java +++ b/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsActivity.java @@ -7,7 +7,6 @@ import android.content.res.Resources; import android.os.Bundle; import android.os.SystemClock; -import android.util.TypedValue; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.Menu; @@ -39,6 +38,7 @@ import me.edgan.redditslide.util.NetworkUtil; import me.edgan.redditslide.util.OnSingleClickListener; import me.edgan.redditslide.util.stubs.SimpleTextWatcher; +import me.edgan.redditslide.util.MiscUtil; import java.util.ArrayList; import java.util.Arrays; @@ -131,11 +131,7 @@ public void onCreate(Bundle savedInstanceState) { applyColorTheme(); setContentView(R.layout.activity_settings); - if (SettingValues.oldSwipeMode) { - TypedValue typedValue = new TypedValue(); - getTheme().resolveAttribute(R.attr.card_background, typedValue, true); - getWindow().getDecorView().setBackgroundColor(typedValue.data); - } + MiscUtil.setupOldSwipeModeBackground(this, getWindow().getDecorView()); setupAppBar(R.id.toolbar, R.string.title_settings, true, true); diff --git a/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsComments.java b/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsComments.java index a3fc0658f..055d3067a 100644 --- a/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsComments.java +++ b/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsComments.java @@ -1,12 +1,11 @@ package me.edgan.redditslide.ui.settings; import android.os.Bundle; -import android.util.TypedValue; import android.view.ViewGroup; import me.edgan.redditslide.Activities.BaseActivityAnim; import me.edgan.redditslide.R; -import me.edgan.redditslide.SettingValues; +import me.edgan.redditslide.util.MiscUtil; public class SettingsComments extends BaseActivityAnim { @@ -17,11 +16,7 @@ public void onCreate(Bundle savedInstanceState) { applyColorTheme(); setContentView(R.layout.activity_settings_comments); - if (SettingValues.oldSwipeMode) { - TypedValue typedValue = new TypedValue(); - getTheme().resolveAttribute(R.attr.card_background, typedValue, true); - getWindow().getDecorView().setBackgroundColor(typedValue.data); - } + MiscUtil.setupOldSwipeModeBackground(this, getWindow().getDecorView()); setupAppBar(R.id.toolbar, R.string.settings_title_comments, true, true); diff --git a/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsData.java b/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsData.java index d31a48b21..7afeac648 100644 --- a/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsData.java +++ b/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsData.java @@ -1,12 +1,11 @@ package me.edgan.redditslide.ui.settings; import android.os.Bundle; -import android.util.TypedValue; import android.view.ViewGroup; import me.edgan.redditslide.Activities.BaseActivityAnim; import me.edgan.redditslide.R; -import me.edgan.redditslide.SettingValues; +import me.edgan.redditslide.util.MiscUtil; /** Created by ccrama on 3/5/2015. */ public class SettingsData extends BaseActivityAnim { @@ -18,11 +17,7 @@ public void onCreate(Bundle savedInstanceState) { applyColorTheme(); setContentView(R.layout.activity_settings_datasaving); - if (SettingValues.oldSwipeMode) { - TypedValue typedValue = new TypedValue(); - getTheme().resolveAttribute(R.attr.card_background, typedValue, true); - getWindow().getDecorView().setBackgroundColor(typedValue.data); - } + MiscUtil.setupOldSwipeModeBackground(this, getWindow().getDecorView()); setupAppBar(R.id.toolbar, R.string.settings_data, true, true); diff --git a/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsFilter.java b/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsFilter.java index 1291f1828..32bea6bc2 100644 --- a/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsFilter.java +++ b/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsFilter.java @@ -7,7 +7,6 @@ import android.text.Spanned; import android.text.style.ForegroundColorSpan; import android.text.style.StyleSpan; -import android.util.TypedValue; import android.view.KeyEvent; import android.view.View; import android.view.inputmethod.EditorInfo; @@ -23,6 +22,7 @@ import me.edgan.redditslide.SettingValues; import me.edgan.redditslide.Visuals.Palette; import me.edgan.redditslide.PostMatch; +import me.edgan.redditslide.util.MiscUtil; import java.util.HashSet; import java.util.Locale; @@ -43,11 +43,7 @@ public void onCreate(Bundle savedInstanceState) { applyColorTheme(); setContentView(R.layout.activity_settings_filters); - if (SettingValues.oldSwipeMode) { - TypedValue typedValue = new TypedValue(); - getTheme().resolveAttribute(R.attr.card_background, typedValue, true); - getWindow().getDecorView().setBackgroundColor(typedValue.data); - } + MiscUtil.setupOldSwipeModeBackground(this, getWindow().getDecorView()); setupAppBar(R.id.toolbar, R.string.settings_title_filter, true, true); diff --git a/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsFont.java b/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsFont.java index 6d31e76c7..a1f6a7eb9 100644 --- a/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsFont.java +++ b/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsFont.java @@ -1,12 +1,11 @@ package me.edgan.redditslide.ui.settings; import android.os.Bundle; -import android.util.TypedValue; import android.view.ViewGroup; import me.edgan.redditslide.Activities.BaseActivityAnim; import me.edgan.redditslide.R; -import me.edgan.redditslide.SettingValues; +import me.edgan.redditslide.util.MiscUtil; /** Created by l3d00m on 11/13/2015. */ public class SettingsFont extends BaseActivityAnim { @@ -18,11 +17,7 @@ public void onCreate(Bundle savedInstanceState) { applyColorTheme(); setContentView(R.layout.activity_settings_font); - if (SettingValues.oldSwipeMode) { - TypedValue typedValue = new TypedValue(); - getTheme().resolveAttribute(R.attr.card_background, typedValue, true); - getWindow().getDecorView().setBackgroundColor(typedValue.data); - } + MiscUtil.setupOldSwipeModeBackground(this, getWindow().getDecorView()); setupAppBar(R.id.toolbar, R.string.settings_title_font, true, true); diff --git a/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsGeneral.java b/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsGeneral.java index da138bc26..dd72a0836 100644 --- a/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsGeneral.java +++ b/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsGeneral.java @@ -5,7 +5,6 @@ import android.net.Uri; import android.os.Build; import android.os.Bundle; -import android.util.TypedValue; import android.view.ViewGroup; import android.widget.RelativeLayout; import android.widget.TextView; @@ -17,6 +16,7 @@ import me.edgan.redditslide.SettingValues; import me.edgan.redditslide.util.LogUtil; import me.edgan.redditslide.util.StorageUtil; +import me.edgan.redditslide.util.MiscUtil; /** Created by ccrama on 3/5/2015. */ public class SettingsGeneral extends BaseActivityAnim implements StorageUtil.DirectoryChooserHost { @@ -30,11 +30,7 @@ public void onCreate(Bundle savedInstanceState) { applyColorTheme(); setContentView(R.layout.activity_settings_general); - if (SettingValues.oldSwipeMode) { - TypedValue typedValue = new TypedValue(); - getTheme().resolveAttribute(R.attr.card_background, typedValue, true); - getWindow().getDecorView().setBackgroundColor(typedValue.data); - } + MiscUtil.setupOldSwipeModeBackground(this, getWindow().getDecorView()); setupAppBar(R.id.toolbar, R.string.settings_title_general, true, true); diff --git a/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsHandling.java b/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsHandling.java index 2ed25b298..bdf8ed71d 100644 --- a/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsHandling.java +++ b/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsHandling.java @@ -2,12 +2,12 @@ import android.content.SharedPreferences; import android.os.Bundle; -import android.util.TypedValue; import android.view.ViewGroup; import me.edgan.redditslide.Activities.BaseActivityAnim; import me.edgan.redditslide.R; import me.edgan.redditslide.SettingValues; +import me.edgan.redditslide.util.MiscUtil; /** Created by l3d00m on 11/13/2015. */ public class SettingsHandling extends BaseActivityAnim { @@ -19,11 +19,7 @@ public void onCreate(Bundle savedInstanceState) { applyColorTheme(); setContentView(R.layout.activity_settings_handling); - if (SettingValues.oldSwipeMode) { - TypedValue typedValue = new TypedValue(); - getTheme().resolveAttribute(R.attr.card_background, typedValue, true); - getWindow().getDecorView().setBackgroundColor(typedValue.data); - } + MiscUtil.setupOldSwipeModeBackground(this, getWindow().getDecorView()); setupAppBar(R.id.toolbar, R.string.settings_link_handling, true, true); diff --git a/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsHistory.java b/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsHistory.java index c8f324379..5473364a1 100644 --- a/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsHistory.java +++ b/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsHistory.java @@ -1,12 +1,11 @@ package me.edgan.redditslide.ui.settings; import android.os.Bundle; -import android.util.TypedValue; import android.view.ViewGroup; import me.edgan.redditslide.Activities.BaseActivityAnim; import me.edgan.redditslide.R; -import me.edgan.redditslide.SettingValues; +import me.edgan.redditslide.util.MiscUtil; public class SettingsHistory extends BaseActivityAnim { @@ -18,11 +17,7 @@ protected void onCreate(Bundle savedInstanceState) { applyColorTheme(); setContentView(R.layout.activity_settings_history); - if (SettingValues.oldSwipeMode) { - TypedValue typedValue = new TypedValue(); - getTheme().resolveAttribute(R.attr.card_background, typedValue, true); - getWindow().getDecorView().setBackgroundColor(typedValue.data); - } + MiscUtil.setupOldSwipeModeBackground(this, getWindow().getDecorView()); setupAppBar(R.id.toolbar, R.string.settings_title_history, true, true); diff --git a/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsLibs.java b/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsLibs.java index 56309f1c6..6854b740b 100644 --- a/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsLibs.java +++ b/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsLibs.java @@ -1,14 +1,13 @@ package me.edgan.redditslide.ui.settings; import android.os.Bundle; -import android.util.TypedValue; import com.mikepenz.aboutlibraries.LibsBuilder; import com.mikepenz.aboutlibraries.ui.LibsSupportFragment; import me.edgan.redditslide.Activities.BaseActivityAnim; import me.edgan.redditslide.R; -import me.edgan.redditslide.SettingValues; +import me.edgan.redditslide.util.MiscUtil; public class SettingsLibs extends BaseActivityAnim { @@ -18,11 +17,7 @@ protected void onCreate(Bundle savedInstanceState) { applyColorTheme(); setContentView(R.layout.activity_settings_libs); - if (SettingValues.oldSwipeMode) { - TypedValue typedValue = new TypedValue(); - getTheme().resolveAttribute(R.attr.card_background, typedValue, true); - getWindow().getDecorView().setBackgroundColor(typedValue.data); - } + MiscUtil.setupOldSwipeModeBackground(this, getWindow().getDecorView()); setupAppBar(R.id.toolbar, R.string.settings_about_libs, true, true); diff --git a/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsModeration.java b/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsModeration.java index 70a2f0924..8f4c50374 100644 --- a/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsModeration.java +++ b/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsModeration.java @@ -1,12 +1,11 @@ package me.edgan.redditslide.ui.settings; import android.os.Bundle; -import android.util.TypedValue; import android.view.ViewGroup; import me.edgan.redditslide.Activities.BaseActivityAnim; import me.edgan.redditslide.R; -import me.edgan.redditslide.SettingValues; +import me.edgan.redditslide.util.MiscUtil; public class SettingsModeration extends BaseActivityAnim { private SettingsModerationFragment fragment = new SettingsModerationFragment(this); @@ -17,11 +16,7 @@ protected void onCreate(Bundle savedInstanceState) { applyColorTheme(); setContentView(R.layout.activity_settings_moderation); - if (SettingValues.oldSwipeMode) { - TypedValue typedValue = new TypedValue(); - getTheme().resolveAttribute(R.attr.card_background, typedValue, true); - getWindow().getDecorView().setBackgroundColor(typedValue.data); - } + MiscUtil.setupOldSwipeModeBackground(this, getWindow().getDecorView()); setupAppBar(R.id.toolbar, R.string.settings_moderation, true, true); diff --git a/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsReddit.java b/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsReddit.java index 6e0be2477..d745fc575 100644 --- a/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsReddit.java +++ b/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsReddit.java @@ -1,12 +1,11 @@ package me.edgan.redditslide.ui.settings; import android.os.Bundle; -import android.util.TypedValue; import android.view.ViewGroup; import me.edgan.redditslide.Activities.BaseActivityAnim; import me.edgan.redditslide.R; -import me.edgan.redditslide.SettingValues; +import me.edgan.redditslide.util.MiscUtil; /** Created by l3d00m on 11/13/2015. */ public class SettingsReddit extends BaseActivityAnim { @@ -19,11 +18,7 @@ public void onCreate(Bundle savedInstanceState) { applyColorTheme(); setContentView(R.layout.activity_settings_reddit); - if (SettingValues.oldSwipeMode) { - TypedValue typedValue = new TypedValue(); - getTheme().resolveAttribute(R.attr.card_background, typedValue, true); - getWindow().getDecorView().setBackgroundColor(typedValue.data); - } + MiscUtil.setupOldSwipeModeBackground(this, getWindow().getDecorView()); setupAppBar(R.id.toolbar, R.string.settings_reddit_prefs, true, true); diff --git a/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsSubreddit.java b/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsSubreddit.java index 7f7e053ab..854534cfe 100644 --- a/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsSubreddit.java +++ b/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsSubreddit.java @@ -4,7 +4,6 @@ import android.content.res.Resources; import android.os.AsyncTask; import android.os.Bundle; -import android.util.TypedValue; import android.view.View; import androidx.appcompat.app.AlertDialog; @@ -25,6 +24,7 @@ import me.edgan.redditslide.Visuals.GetClosestColor; import me.edgan.redditslide.Visuals.Palette; import me.edgan.redditslide.util.LayoutUtils; +import me.edgan.redditslide.util.MiscUtil; import net.dean.jraw.models.Subreddit; @@ -61,11 +61,7 @@ public void onCreate(Bundle savedInstanceState) { applyColorTheme(); setContentView(R.layout.activity_settings_subreddit); - if (SettingValues.oldSwipeMode) { - TypedValue typedValue = new TypedValue(); - getTheme().resolveAttribute(R.attr.card_background, typedValue, true); - getWindow().getDecorView().setBackgroundColor(typedValue.data); - } + MiscUtil.setupOldSwipeModeBackground(this, getWindow().getDecorView()); SettingsThemeFragment.changed = true; diff --git a/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsSynccit.java b/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsSynccit.java index e3c37f383..3b70d3e1e 100644 --- a/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsSynccit.java +++ b/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsSynccit.java @@ -3,7 +3,6 @@ import android.app.Dialog; import android.content.SharedPreferences; import android.os.Bundle; -import android.util.TypedValue; import android.view.View; import android.widget.EditText; @@ -17,6 +16,7 @@ import me.edgan.redditslide.Synccit.MySynccitReadTask; import me.edgan.redditslide.Synccit.MySynccitUpdateTask; import me.edgan.redditslide.Synccit.SynccitRead; +import me.edgan.redditslide.util.MiscUtil; import java.util.Collections; @@ -31,11 +31,7 @@ public void onCreate(Bundle savedInstanceState) { applyColorTheme(); setContentView(R.layout.activity_settings_synccit); - if (SettingValues.oldSwipeMode) { - TypedValue typedValue = new TypedValue(); - getTheme().resolveAttribute(R.attr.card_background, typedValue, true); - getWindow().getDecorView().setBackgroundColor(typedValue.data); - } + MiscUtil.setupOldSwipeModeBackground(this, getWindow().getDecorView()); setupAppBar(R.id.toolbar, R.string.settings_synccit, true, true); diff --git a/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsTheme.java b/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsTheme.java index 5c5d423fe..5e3c66b39 100644 --- a/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsTheme.java +++ b/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsTheme.java @@ -2,12 +2,11 @@ import android.content.Intent; import android.os.Bundle; -import android.util.TypedValue; import android.view.ViewGroup; import me.edgan.redditslide.Activities.BaseActivityAnim; import me.edgan.redditslide.R; -import me.edgan.redditslide.SettingValues; +import me.edgan.redditslide.util.MiscUtil; /** Created by ccrama on 3/5/2015. */ public class SettingsTheme extends BaseActivityAnim implements RestartActivity { @@ -19,11 +18,7 @@ public void onCreate(Bundle savedInstanceState) { applyColorTheme(); setContentView(R.layout.activity_settings_theme); - if (SettingValues.oldSwipeMode) { - TypedValue typedValue = new TypedValue(); - getTheme().resolveAttribute(R.attr.card_background, typedValue, true); - getWindow().getDecorView().setBackgroundColor(typedValue.data); - } + MiscUtil.setupOldSwipeModeBackground(this, getWindow().getDecorView()); setupAppBar(R.id.toolbar, R.string.title_edit_theme, true, true); diff --git a/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsViewType.java b/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsViewType.java index ee6af29a0..44dc725a1 100644 --- a/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsViewType.java +++ b/app/src/main/java/me/edgan/redditslide/ui/settings/SettingsViewType.java @@ -1,7 +1,6 @@ package me.edgan.redditslide.ui.settings; import android.os.Bundle; -import android.util.TypedValue; import android.view.MenuItem; import android.view.View; import android.widget.TextView; @@ -11,6 +10,7 @@ import me.edgan.redditslide.Activities.BaseActivityAnim; import me.edgan.redditslide.R; import me.edgan.redditslide.SettingValues; +import me.edgan.redditslide.util.MiscUtil; /** Created by ccrama on 3/5/2015. */ public class SettingsViewType extends BaseActivityAnim { @@ -20,11 +20,7 @@ public void onCreate(Bundle savedInstanceState) { applyColorTheme(); setContentView(R.layout.activity_settings_viewtype); - if (SettingValues.oldSwipeMode) { - TypedValue typedValue = new TypedValue(); - getTheme().resolveAttribute(R.attr.card_background, typedValue, true); - getWindow().getDecorView().setBackgroundColor(typedValue.data); - } + MiscUtil.setupOldSwipeModeBackground(this, getWindow().getDecorView()); setupAppBar(R.id.toolbar, R.string.settings_view_type, true, true); diff --git a/app/src/main/java/me/edgan/redditslide/ui/settings/dragSort/ReorderSubreddits.java b/app/src/main/java/me/edgan/redditslide/ui/settings/dragSort/ReorderSubreddits.java index 2e9b50287..1da087c2a 100644 --- a/app/src/main/java/me/edgan/redditslide/ui/settings/dragSort/ReorderSubreddits.java +++ b/app/src/main/java/me/edgan/redditslide/ui/settings/dragSort/ReorderSubreddits.java @@ -11,7 +11,6 @@ import android.text.Editable; import android.text.TextWatcher; import android.util.Log; -import android.util.TypedValue; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; @@ -48,6 +47,7 @@ import me.edgan.redditslide.util.BlendModeUtil; import me.edgan.redditslide.util.DialogUtil; import me.edgan.redditslide.util.DisplayUtil; +import me.edgan.redditslide.util.MiscUtil; import net.dean.jraw.http.MultiRedditUpdateRequest; import net.dean.jraw.managers.MultiRedditManager; @@ -225,11 +225,7 @@ protected void onCreate(Bundle savedInstanceState) { applyColorTheme(); setContentView(R.layout.activity_sort); - if (SettingValues.oldSwipeMode) { - TypedValue typedValue = new TypedValue(); - getTheme().resolveAttribute(R.attr.card_background, typedValue, true); - getWindow().getDecorView().setBackgroundColor(typedValue.data); - } + MiscUtil.setupOldSwipeModeBackground(this, getWindow().getDecorView()); setupAppBar(R.id.toolbar, R.string.settings_manage_subscriptions, false, true); mToolbar.setPopupTheme(new ColorPreferences(this).getFontStyle().getBaseId()); diff --git a/app/src/main/java/me/edgan/redditslide/util/MiscUtil.java b/app/src/main/java/me/edgan/redditslide/util/MiscUtil.java index 3b03d854f..204968e34 100644 --- a/app/src/main/java/me/edgan/redditslide/util/MiscUtil.java +++ b/app/src/main/java/me/edgan/redditslide/util/MiscUtil.java @@ -9,9 +9,11 @@ import android.text.style.RelativeSizeSpan; import android.widget.TextView; + import me.edgan.redditslide.Adapters.ProfileCommentViewHolder; import me.edgan.redditslide.Authentication; import me.edgan.redditslide.R; +import me.edgan.redditslide.SettingValues; /** * Created by TacoTheDank on 03/15/2021. @@ -124,4 +126,13 @@ public static void addSearchAwards( titleString.append(" "); } } + + public static void setupOldSwipeModeBackground(Context context, android.view.View view) { + if (SettingValues.oldSwipeMode) { + // Set an opaque background for the View + android.util.TypedValue typedValue = new android.util.TypedValue(); + context.getTheme().resolveAttribute(R.attr.card_background, typedValue, true); + view.setBackgroundColor(typedValue.data); + } + } } diff --git a/app/src/noGPlay/java/me/edgan/redditslide/ui/settings/SettingsBackup.java b/app/src/noGPlay/java/me/edgan/redditslide/ui/settings/SettingsBackup.java index 2116f5526..1292af656 100644 --- a/app/src/noGPlay/java/me/edgan/redditslide/ui/settings/SettingsBackup.java +++ b/app/src/noGPlay/java/me/edgan/redditslide/ui/settings/SettingsBackup.java @@ -7,7 +7,6 @@ import android.os.Bundle; import android.provider.DocumentsContract; import android.util.Log; -import android.util.TypedValue; import androidx.appcompat.app.AlertDialog; import androidx.documentfile.provider.DocumentFile; @@ -20,6 +19,7 @@ import me.edgan.redditslide.R; import me.edgan.redditslide.util.LayoutUtils; import me.edgan.redditslide.util.StorageUtil; +import me.edgan.redditslide.util.MiscUtil; import java.io.BufferedReader; import java.io.BufferedWriter; @@ -48,8 +48,7 @@ public class SettingsBackup extends BaseActivityAnim { // Progress dialog private MaterialDialog progress; - // We’ll store the final URI of the newly created local backup file so we can offer to "View" - // it. + // We’ll store the final URI of the newly created local backup file so we can offer to "View" it. private Uri localBackupFileUri = null; @Override @@ -58,11 +57,7 @@ protected void onCreate(Bundle savedInstanceState) { applyColorTheme(); setContentView(R.layout.activity_settings_sync); - if (SettingValues.oldSwipeMode) { - TypedValue typedValue = new TypedValue(); - getTheme().resolveAttribute(R.attr.card_background, typedValue, true); - getWindow().getDecorView().setBackgroundColor(typedValue.data); - } + MiscUtil.setupOldSwipeModeBackground(this, getWindow().getDecorView()); setupAppBar(R.id.toolbar, R.string.settings_title_backup, true, true); diff --git a/app/src/withGPlay/java/me/edgan/redditslide/ui/settings/SettingsBackup.java b/app/src/withGPlay/java/me/edgan/redditslide/ui/settings/SettingsBackup.java index fe6598a42..942e47767 100644 --- a/app/src/withGPlay/java/me/edgan/redditslide/ui/settings/SettingsBackup.java +++ b/app/src/withGPlay/java/me/edgan/redditslide/ui/settings/SettingsBackup.java @@ -7,7 +7,6 @@ import android.os.Bundle; import android.provider.DocumentsContract; import android.util.Log; -import android.util.TypedValue; import androidx.annotation.NonNull; import androidx.appcompat.app.AlertDialog; @@ -35,9 +34,9 @@ import me.edgan.redditslide.Activities.BaseActivityAnim; import me.edgan.redditslide.R; -import me.edgan.redditslide.SettingValues; import me.edgan.redditslide.util.LayoutUtils; import me.edgan.redditslide.util.StorageUtil; +import me.edgan.redditslide.util.MiscUtil; import java.io.BufferedReader; import java.io.BufferedWriter; @@ -88,8 +87,7 @@ public class SettingsBackup extends BaseActivityAnim { private boolean backupRequestedAfterSignIn = false; private boolean restoreRequestedAfterSignIn = false; - // We’ll store the final URI of the newly created local backup file so we can offer to "View" - // it. + // We’ll store the final URI of the newly created local backup file so we can offer to "View" it private Uri localBackupFileUri = null; @Override @@ -99,12 +97,7 @@ protected void onCreate(Bundle savedInstanceState) { applyColorTheme(); setContentView(R.layout.activity_settings_sync); - - if (SettingValues.oldSwipeMode) { - TypedValue typedValue = new TypedValue(); - getTheme().resolveAttribute(R.attr.card_background, typedValue, true); - getWindow().getDecorView().setBackgroundColor(typedValue.data); - } + MiscUtil.setupOldSwipeModeBackground(this, getWindow().getDecorView()); setupAppBar(R.id.toolbar, R.string.settings_title_backup, true, true); From dd47e78151c3088e85460d9b1af908051c89b0d2 Mon Sep 17 00:00:00 2001 From: Nathan Grennan Date: Sat, 24 May 2025 23:41:50 -0700 Subject: [PATCH 70/99] Updated versionCode in app/build.gradle Updated CHANGELOG.md --- CHANGELOG.md | 8 ++++++++ app/build.gradle | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9546ae5eb..7017ac41d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,14 @@ The old changelog can be read in the [CHANGELOG.md](https://github.com/Haptic-Ap --- +7.3.2 / 2025-5-24 +============ +* Fixed the cascade effect while using oldSwipeMode +* Add playback speed control to video player +* Inconsistent tap behavior on comments with media +* Fixed an intermittent display issue with Selftext preview images +* Tumblr gif fixes + 7.3.1 / 2025-5-9 =========== * Dismiss toolbar search and keyboard on suggestion selection diff --git a/app/build.gradle b/app/build.gradle index 662589293..785c58e83 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -49,7 +49,7 @@ android { multiDexEnabled true targetSdk 34 vectorDrawables.useSupportLibrary true - versionCode 731 + versionCode 732 versionName androidGitVersion.name() } From 482fb004c727897146f7a2d1c40e8364f7bebbfb Mon Sep 17 00:00:00 2001 From: PrimaryVision <155949587+PrimaryVision@users.noreply.github.com> Date: Sun, 25 May 2025 22:52:52 +0530 Subject: [PATCH 71/99] Fix Crash: NullPointerException when entering Special Tabs with "Hide subscribed subreddit tabs" enabled --- .../java/me/edgan/redditslide/Adapters/SideArrayAdapter.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/me/edgan/redditslide/Adapters/SideArrayAdapter.java b/app/src/main/java/me/edgan/redditslide/Adapters/SideArrayAdapter.java index aeace2311..732596430 100644 --- a/app/src/main/java/me/edgan/redditslide/Adapters/SideArrayAdapter.java +++ b/app/src/main/java/me/edgan/redditslide/Adapters/SideArrayAdapter.java @@ -203,7 +203,9 @@ public void onClick(View view) { int pos = mainActivity.usedArray.indexOf(subreddit); mainActivity.pager.setCurrentItem(pos); mainActivity.drawerLayout.closeDrawers(); - ((MainActivity) getContext()).drawerSearch.setText(""); + if (((MainActivity) getContext()).drawerSearch != null) { + ((MainActivity) getContext()).drawerSearch.setText(""); + } } else if (subreddit.equalsIgnoreCase("random") || subreddit.equalsIgnoreCase("randnsfw") || subreddit.equalsIgnoreCase("myrandom")) { From 207bb8643ed9c870ac7c5f2368de283bc2e4c5e6 Mon Sep 17 00:00:00 2001 From: Nathan Grennan Date: Sat, 24 May 2025 23:41:50 -0700 Subject: [PATCH 72/99] Updated versionCode in app/build.gradle Updated CHANGELOG.md --- CHANGELOG.md | 8 ++++++++ app/build.gradle | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9546ae5eb..7017ac41d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,14 @@ The old changelog can be read in the [CHANGELOG.md](https://github.com/Haptic-Ap --- +7.3.2 / 2025-5-24 +============ +* Fixed the cascade effect while using oldSwipeMode +* Add playback speed control to video player +* Inconsistent tap behavior on comments with media +* Fixed an intermittent display issue with Selftext preview images +* Tumblr gif fixes + 7.3.1 / 2025-5-9 =========== * Dismiss toolbar search and keyboard on suggestion selection diff --git a/app/build.gradle b/app/build.gradle index 662589293..785c58e83 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -49,7 +49,7 @@ android { multiDexEnabled true targetSdk 34 vectorDrawables.useSupportLibrary true - versionCode 731 + versionCode 732 versionName androidGitVersion.name() } From 95dea6f6357d6e82ae06a1b5bf3ec10e53fdc16f Mon Sep 17 00:00:00 2001 From: Nathan Grennan Date: Mon, 26 May 2025 00:50:58 -0700 Subject: [PATCH 73/99] Fixed NullPointerException in me.edgan.redditslide.Activities.MainActivity.onOptionsItemSelected --- .../java/me/edgan/redditslide/Activities/MainActivity.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/src/main/java/me/edgan/redditslide/Activities/MainActivity.java b/app/src/main/java/me/edgan/redditslide/Activities/MainActivity.java index 64e839146..bd9e978b8 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/MainActivity.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/MainActivity.java @@ -483,6 +483,10 @@ public boolean onMenuItemClick(MenuItem item) { @Override public boolean onOptionsItemSelected(MenuItem item) { + if (usedArray == null || usedArray.isEmpty() || Reddit.currentPosition < 0 || Reddit.currentPosition >= usedArray.size()) { + return super.onOptionsItemSelected(item); + } + final String subreddit = usedArray.get(Reddit.currentPosition); // Add null checks to prevent NullPointerException From e1d804f0149759ab03793497cc84930c2b4b57e5 Mon Sep 17 00:00:00 2001 From: Nathan Grennan Date: Mon, 26 May 2025 01:04:11 -0700 Subject: [PATCH 74/99] Fixed NullPointerException in me.edgan.redditslide.Activities.GalleryImage. --- .../me/edgan/redditslide/Activities/GalleryImage.java | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/me/edgan/redditslide/Activities/GalleryImage.java b/app/src/main/java/me/edgan/redditslide/Activities/GalleryImage.java index cfbed90e7..f317a2d52 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/GalleryImage.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/GalleryImage.java @@ -19,6 +19,14 @@ public class GalleryImage implements Serializable { public MediaMetadata metadata; public GalleryImage(JsonNode data) { + // Add metadata population + this.metadata = new MediaMetadata(); + + if (data == null || data.isNull()) { + Log.e(TAG, "GalleryImage constructor called with null or JsonNull data. Initializing as empty."); + return; + } + Log.d(TAG, "GalleryImage constructor called with data: " + data.toString()); if (data.has("media_id")) { mediaId = data.get("media_id").asText(); @@ -42,8 +50,7 @@ public GalleryImage(JsonNode data) { height = sNode.get("y").asInt(); } - // Add metadata population - metadata = new MediaMetadata(); + if (data.has("e")) { metadata.e = data.get("e").asText(); } From e7e356248dfb0559fe7bf7f170c7ef1b6f1421f3 Mon Sep 17 00:00:00 2001 From: Nathan Grennan Date: Mon, 26 May 2025 01:39:33 -0700 Subject: [PATCH 75/99] Fixed off-by-one bug in Reddit Gallery in horizontal mode --- .../redditslide/Activities/RedditGalleryPager.java | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/me/edgan/redditslide/Activities/RedditGalleryPager.java b/app/src/main/java/me/edgan/redditslide/Activities/RedditGalleryPager.java index 28ebc8c5e..c82589ce2 100644 --- a/app/src/main/java/me/edgan/redditslide/Activities/RedditGalleryPager.java +++ b/app/src/main/java/me/edgan/redditslide/Activities/RedditGalleryPager.java @@ -320,7 +320,17 @@ private void showGridView() { gridview.setOnItemClickListener( (parent, v, position, id) -> { - p.setCurrentItem(position); + int targetPosition = position; + if (SettingValues.oldSwipeMode) { + // When oldSwipeMode is on, ViewPager has a blank page at index 0, + // so the actual content starts from index 1. + // The grid items are 0-indexed based on content, so add 1. + targetPosition = position + 1; + } else { + // Without oldSwipeMode, ViewPager items are 0-indexed directly. + targetPosition = position; + } + p.setCurrentItem(targetPosition); d.dismiss(); }); From df66352acdd3e0ec9cb83c7b0e13db4ae0c047d5 Mon Sep 17 00:00:00 2001 From: Nathan Grennan Date: Mon, 26 May 2025 01:40:46 -0700 Subject: [PATCH 76/99] Fixed off-by-one bug in Reddit Gallery in vertical mode --- .../Adapters/RedditGalleryView.java | 44 +++++++++++++++++-- 1 file changed, 40 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/me/edgan/redditslide/Adapters/RedditGalleryView.java b/app/src/main/java/me/edgan/redditslide/Adapters/RedditGalleryView.java index 6f5f35589..f629b5e71 100644 --- a/app/src/main/java/me/edgan/redditslide/Adapters/RedditGalleryView.java +++ b/app/src/main/java/me/edgan/redditslide/Adapters/RedditGalleryView.java @@ -98,14 +98,50 @@ public void onItemClick(AdapterView parent, View v, int position, long id) { position + 1, context.findViewById(R.id.toolbar).getHeight()); } else if (imagesView instanceof RecyclerView) { - // Just scroll the RecyclerView + // This case is when R.id.images in the context's layout is a RecyclerView. + // For RedditGallery, R.id.images is a ViewPager, so this branch is not hit. + // Original logic for this case: ((LinearLayoutManager)((RecyclerView) imagesView).getLayoutManager()) .scrollToPositionWithOffset( - position + 1, + position + 1, // Grid position is 0-indexed for images context.findViewById(R.id.toolbar).getHeight()); } else if (imagesView instanceof ViewPager) { - // Or if it's a ViewPager - ((ViewPager) imagesView).setCurrentItem(position); + if (context instanceof RedditGallery) { // Specific fix for RedditGallery (vertical album) + ViewPager mainViewPagerInRedditGallery = (ViewPager) imagesView; + RedditGallery redditGalleryActivity = (RedditGallery) context; + + // Determine the ViewPager page index for AlbumFrag + int albumFragPageIndexInViewPager = SettingValues.oldSwipeMode ? 1 : 0; + + // Ensure ViewPager is showing AlbumFrag + if (mainViewPagerInRedditGallery.getCurrentItem() != albumFragPageIndexInViewPager) { + mainViewPagerInRedditGallery.setCurrentItem(albumFragPageIndexInViewPager, false); + } + + // Get AlbumFrag's RecyclerView and scroll it + RedditGallery.RedditGalleryPagerAdapter pagerAdapter = (RedditGallery.RedditGalleryPagerAdapter) mainViewPagerInRedditGallery.getAdapter(); + if (pagerAdapter != null && pagerAdapter.gallery != null && pagerAdapter.gallery.recyclerView != null) { + RedditGallery.AlbumFrag albumFrag = pagerAdapter.gallery; + // Grid 'position' is 0-indexed for images. + // AlbumFrag's RecyclerView has a spacer at its index 0. Image 0 is at RecyclerView index 1. + int scrollToRecyclerPosition = position + 1; + + // Offset should be the height of the toolbar AlbumFrag is using. + // AlbumFrag configures redditGalleryActivity.mToolbar as its action bar. + int offset = 0; + if (redditGalleryActivity.mToolbar != null) { + offset = redditGalleryActivity.mToolbar.getHeight(); + } + + ((LinearLayoutManager) albumFrag.recyclerView.getLayoutManager()) + .scrollToPositionWithOffset(scrollToRecyclerPosition, offset); + } + } else { + // Fallback for other contexts where R.id.images is a ViewPager + // (e.g., potentially AlbumPager, though it has its own grid handler) + // Original behavior for generic ViewPager: + ((ViewPager) imagesView).setCurrentItem(position); + } } d.dismiss(); } From 32f308b48372df7a2cc37076d96bac8b80ad46c6 Mon Sep 17 00:00:00 2001 From: Nathan Grennan Date: Mon, 26 May 2025 01:41:05 -0700 Subject: [PATCH 77/99] Fixed NullPointerException in me.edgan.redditslide.Adapters.TumblrView.onBindViewHolder --- .../redditslide/Adapters/TumblrView.java | 44 ++++++++++++++----- 1 file changed, 33 insertions(+), 11 deletions(-) diff --git a/app/src/main/java/me/edgan/redditslide/Adapters/TumblrView.java b/app/src/main/java/me/edgan/redditslide/Adapters/TumblrView.java index 6669e5331..1f42f987e 100644 --- a/app/src/main/java/me/edgan/redditslide/Adapters/TumblrView.java +++ b/app/src/main/java/me/edgan/redditslide/Adapters/TumblrView.java @@ -195,8 +195,13 @@ public void onBindViewHolder(RecyclerView.ViewHolder holder, int i) { user.getOriginalSize().getUrl(), albumHolder.image, ImageGridAdapter.options); - albumHolder.body.setVisibility(View.VISIBLE); - albumHolder.text.setVisibility(View.VISIBLE); + + if (albumHolder.body != null) { + albumHolder.body.setVisibility(View.VISIBLE); + } + if (albumHolder.text != null) { + albumHolder.text.setVisibility(View.VISIBLE); + } View imageView = albumHolder.image; if (user.getOriginalSize().getWidth() > 0 && user.getOriginalSize().getHeight() > 0) { @@ -221,7 +226,7 @@ public void onBindViewHolder(RecyclerView.ViewHolder holder, int i) { RelativeLayout.LayoutParams.WRAP_CONTENT)); } - { + if (albumHolder.body != null) { int type = new FontPreferences(albumHolder.body.getContext()) .getFontTypeComment() @@ -234,7 +239,7 @@ public void onBindViewHolder(RecyclerView.ViewHolder holder, int i) { } albumHolder.body.setTypeface(typeface); } - { + if (albumHolder.text != null) { int type = new FontPreferences(albumHolder.text.getContext()) .getFontTypeTitle() @@ -251,17 +256,34 @@ public void onBindViewHolder(RecyclerView.ViewHolder holder, int i) { if (user.getCaption() != null) { List textBlocks = SubmissionParser.getBlocks(user.getCaption()); String captionText = textBlocks.get(0).trim(); - LinkUtil.setTextWithLinks(captionText, albumHolder.body); - albumHolder.text.setVisibility(View.GONE); + if (albumHolder.body != null) { + LinkUtil.setTextWithLinks(captionText, albumHolder.body); + } + if (albumHolder.text != null) { + albumHolder.text.setVisibility(View.GONE); + } - if (albumHolder.body.getText().toString().isEmpty()) { - albumHolder.body.setVisibility(View.GONE); + boolean bodyIsEmpty = true; + if (albumHolder.body != null && albumHolder.body.getText() != null) { + bodyIsEmpty = albumHolder.body.getText().toString().isEmpty(); + } + + if (bodyIsEmpty) { + if (albumHolder.body != null) { + albumHolder.body.setVisibility(View.GONE); + } } else { - albumHolder.body.setVisibility(View.VISIBLE); + if (albumHolder.body != null) { + albumHolder.body.setVisibility(View.VISIBLE); + } } } else { - albumHolder.text.setVisibility(View.GONE); - albumHolder.body.setVisibility(View.GONE); + if (albumHolder.text != null) { + albumHolder.text.setVisibility(View.GONE); + } + if (albumHolder.body != null) { + albumHolder.body.setVisibility(View.GONE); + } } albumHolder.itemView.setOnClickListener(new View.OnClickListener() { From 53086de7e7c0c2092cc62cbf07f6941e784a3127 Mon Sep 17 00:00:00 2001 From: PrimaryVision <155949587+PrimaryVision@users.noreply.github.com> Date: Tue, 27 May 2025 22:33:42 +0530 Subject: [PATCH 78/99] feat(settings): replace small tag toggle with selection menu --- .../me/edgan/redditslide/SettingValues.java | 2 +- .../ui/settings/EditCardsLayout.java | 69 ++++++++++++++++--- .../layout/activity_settings_theme_card.xml | 33 +++------ app/src/main/res/menu/small_tag_mode.xml | 9 +++ app/src/main/res/values/strings.xml | 2 + 5 files changed, 82 insertions(+), 33 deletions(-) create mode 100644 app/src/main/res/menu/small_tag_mode.xml diff --git a/app/src/main/java/me/edgan/redditslide/SettingValues.java b/app/src/main/java/me/edgan/redditslide/SettingValues.java index fb69606f6..c1610083f 100644 --- a/app/src/main/java/me/edgan/redditslide/SettingValues.java +++ b/app/src/main/java/me/edgan/redditslide/SettingValues.java @@ -427,7 +427,7 @@ public static void setAllValues(SharedPreferences settings) { alphabetizeOnSubscribe = prefs.getBoolean(PREF_ALPHABETIZE_SUBSCRIBE, false); commentPager = prefs.getBoolean(PREF_COMMENT_PAGER, false); - smallTag = prefs.getBoolean(PREF_SMALL_TAG, false); + smallTag = prefs.getBoolean(PREF_SMALL_TAG, true); swap = prefs.getBoolean(PREF_SWAP, false); hideSelftextLeadImage = prefs.getBoolean(PREF_SELFTEXT_IMAGE_COMMENT, false); image = prefs.getBoolean(PREF_IMAGE, true); diff --git a/app/src/main/java/me/edgan/redditslide/ui/settings/EditCardsLayout.java b/app/src/main/java/me/edgan/redditslide/ui/settings/EditCardsLayout.java index 64780fc1c..e276b89f5 100644 --- a/app/src/main/java/me/edgan/redditslide/ui/settings/EditCardsLayout.java +++ b/app/src/main/java/me/edgan/redditslide/ui/settings/EditCardsLayout.java @@ -574,17 +574,66 @@ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { }); // Smaller tags// - final SwitchCompat smallTag = (SwitchCompat) findViewById(R.id.tagsetting); + ((TextView) findViewById(R.id.small_tag_current)) + .setText( + SettingValues.smallTag + ? getString(R.string.show_top_right) + : getString(R.string.disabled)); - smallTag.setChecked(SettingValues.smallTag); - smallTag.setOnCheckedChangeListener( - new SwitchCompat.OnCheckedChangeListener() { - @Override - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - layout.removeAllViews(); - layout.addView(CreateCardView.setSmallTag(isChecked, layout)); - } - }); + findViewById(R.id.small_tag_layout) + .setOnClickListener( + new View.OnClickListener() { + @Override + public void onClick(View v) { + PopupMenu popup = new PopupMenu(EditCardsLayout.this, v); + popup.getMenuInflater() + .inflate(R.menu.small_tag_mode, popup.getMenu()); + + popup.setOnMenuItemClickListener( + new PopupMenu.OnMenuItemClickListener() { + public boolean onMenuItemClick(MenuItem item) { + switch (item.getItemId()) { + case R.id.disabled: + SettingValues.smallTag = false; + SettingValues.prefs + .edit() + .putBoolean( + SettingValues.PREF_SMALL_TAG, + false) + .apply(); + ((TextView) findViewById(R.id.small_tag_current)) + .setText(R.string.small_tag_disabled); + break; + case R.id.show_top_right: + SettingValues.smallTag = true; + SettingValues.prefs + .edit() + .putBoolean( + SettingValues + .PREF_SMALL_TAG, + true) + .apply(); + layout.removeAllViews(); + layout.addView( + CreateCardView.setSmallTag( + true, layout)); + break; + } + ((TextView) findViewById(R.id.small_tag_current)) + .setText( + SettingValues.smallTag + ? getString( + R.string + .show_top_right) + : getString( + R.string.disabled)); + return true; + } + }); + + popup.show(); + } + }); // Actionbar// // Enable, collapse// diff --git a/app/src/main/res/layout/activity_settings_theme_card.xml b/app/src/main/res/layout/activity_settings_theme_card.xml index dca2ba660..15560fe33 100644 --- a/app/src/main/res/layout/activity_settings_theme_card.xml +++ b/app/src/main/res/layout/activity_settings_theme_card.xml @@ -307,41 +307,30 @@ android:layout_height="0.25dp" android:alpha=".25" android:background="?attr/tintColor" /> - - - - - - + + +

+ + + \ 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 8b1e37d21..5c5d88e7f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1087,6 +1087,8 @@ Cropped big picture Big picture Press arrow to show actionbar + Show top right + Disabled Long press for actionbar Always show actionbar Subreddit content filter From e1551b885927fcb7e7667fd1acc8a3ee028bc760 Mon Sep 17 00:00:00 2001 From: PrimaryVision <155949587+PrimaryVision@users.noreply.github.com> Date: Tue, 27 May 2025 23:33:36 +0530 Subject: [PATCH 79/99] Add bottom right position --- .../me/edgan/redditslide/SettingValues.java | 8 +++- .../SubmissionViews/HeaderImageLinkView.java | 4 +- .../redditslide/Views/CreateCardView.java | 25 +++++++++-- .../ui/settings/EditCardsLayout.java | 43 +++++++++++++------ .../res/layout/header_image_title_view.xml | 7 +-- app/src/main/res/menu/small_tag_mode.xml | 3 ++ app/src/main/res/values/strings.xml | 1 + 7 files changed, 66 insertions(+), 25 deletions(-) diff --git a/app/src/main/java/me/edgan/redditslide/SettingValues.java b/app/src/main/java/me/edgan/redditslide/SettingValues.java index c1610083f..f6442c1af 100644 --- a/app/src/main/java/me/edgan/redditslide/SettingValues.java +++ b/app/src/main/java/me/edgan/redditslide/SettingValues.java @@ -205,7 +205,7 @@ public class SettingValues { public static boolean commentVolumeNav; public static boolean postNav; public static boolean cropImage; - public static boolean smallTag; + public static int smallTag; public static boolean typeInfoLine; public static boolean votesInfoLine; public static boolean readerMode; @@ -427,7 +427,11 @@ public static void setAllValues(SharedPreferences settings) { alphabetizeOnSubscribe = prefs.getBoolean(PREF_ALPHABETIZE_SUBSCRIBE, false); commentPager = prefs.getBoolean(PREF_COMMENT_PAGER, false); - smallTag = prefs.getBoolean(PREF_SMALL_TAG, true); + // Remove the old boolean preference for smallTag to prevent ClassCastException + if (prefs.contains(PREF_SMALL_TAG) && (prefs.getAll().get(PREF_SMALL_TAG) instanceof Boolean)) { + prefs.edit().remove(PREF_SMALL_TAG).apply(); + } + smallTag = prefs.getInt(PREF_SMALL_TAG, 1); swap = prefs.getBoolean(PREF_SWAP, false); hideSelftextLeadImage = prefs.getBoolean(PREF_SELFTEXT_IMAGE_COMMENT, false); image = prefs.getBoolean(PREF_IMAGE, true); diff --git a/app/src/main/java/me/edgan/redditslide/SubmissionViews/HeaderImageLinkView.java b/app/src/main/java/me/edgan/redditslide/SubmissionViews/HeaderImageLinkView.java index d36123d3e..f0723d61b 100644 --- a/app/src/main/java/me/edgan/redditslide/SubmissionViews/HeaderImageLinkView.java +++ b/app/src/main/java/me/edgan/redditslide/SubmissionViews/HeaderImageLinkView.java @@ -231,7 +231,7 @@ public void doImageAndText(final Submission submission, boolean full, String bas setupTitleAndBottomSheet(submission, full, forceThumb, type); - if (SettingValues.smallTag && !full && !news) { + if (SettingValues.smallTag != 0 && !full && !news) { title = findViewById(R.id.tag); findViewById(R.id.tag).setVisibility(View.VISIBLE); info = null; @@ -241,7 +241,7 @@ public void doImageAndText(final Submission submission, boolean full, String bas info.setVisibility(View.VISIBLE); } - if (SettingValues.smallTag && !full && !news) { + if (SettingValues.smallTag != 0 && !full && !news) { ((TransparentTagTextView) title).init(getContext()); } diff --git a/app/src/main/java/me/edgan/redditslide/Views/CreateCardView.java b/app/src/main/java/me/edgan/redditslide/Views/CreateCardView.java index 1b9d904c9..84ffe1397 100644 --- a/app/src/main/java/me/edgan/redditslide/Views/CreateCardView.java +++ b/app/src/main/java/me/edgan/redditslide/Views/CreateCardView.java @@ -203,10 +203,10 @@ public static View setActionbarVisible(boolean isChecked, ViewGroup parent) { return CreateView(parent); } - public static View setSmallTag(boolean isChecked, ViewGroup parent) { + public static View setSmallTag(int value, ViewGroup parent) { - SettingValues.prefs.edit().putBoolean(SettingValues.PREF_SMALL_TAG, isChecked).apply(); - SettingValues.smallTag = isChecked; + SettingValues.prefs.edit().putInt(SettingValues.PREF_SMALL_TAG, value).apply(); + SettingValues.smallTag = value; return CreateView(parent); } @@ -316,7 +316,24 @@ public static void toggleActionbar(View v) { } private static void doHideObjects(final View v) { - if (SettingValues.smallTag) { + if (SettingValues.smallTag == 1) { + RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) v.findViewById(R.id.tag).getLayoutParams(); + layoutParams.addRule(RelativeLayout.ALIGN_TOP, R.id.leadimage); + layoutParams.addRule(RelativeLayout.ALIGN_RIGHT, R.id.leadimage); + layoutParams.removeRule(RelativeLayout.ALIGN_BOTTOM); + layoutParams.removeRule(RelativeLayout.ALIGN_PARENT_RIGHT); + v.findViewById(R.id.tag).setLayoutParams(layoutParams); + ((RelativeLayout.LayoutParams) v.findViewById(R.id.tag).getLayoutParams()).setMargins(DisplayUtil.dpToPxVertical(10), DisplayUtil.dpToPxVertical(10), DisplayUtil.dpToPxVertical(10), DisplayUtil.dpToPxVertical(10)); + v.findViewById(R.id.base).setVisibility(View.GONE); + v.findViewById(R.id.tag).setVisibility(View.VISIBLE); + } else if (SettingValues.smallTag == 2) { + RelativeLayout.LayoutParams layoutParams = (RelativeLayout.LayoutParams) v.findViewById(R.id.tag).getLayoutParams(); + layoutParams.addRule(RelativeLayout.ALIGN_BOTTOM, R.id.leadimage); + layoutParams.addRule(RelativeLayout.ALIGN_RIGHT, R.id.leadimage); + layoutParams.removeRule(RelativeLayout.ALIGN_TOP); + layoutParams.removeRule(RelativeLayout.ALIGN_PARENT_RIGHT); + v.findViewById(R.id.tag).setLayoutParams(layoutParams); + ((RelativeLayout.LayoutParams) v.findViewById(R.id.tag).getLayoutParams()).setMargins(DisplayUtil.dpToPxVertical(10), DisplayUtil.dpToPxVertical(10), DisplayUtil.dpToPxVertical(10), DisplayUtil.dpToPxVertical(10)); v.findViewById(R.id.base).setVisibility(View.GONE); v.findViewById(R.id.tag).setVisibility(View.VISIBLE); } else { diff --git a/app/src/main/java/me/edgan/redditslide/ui/settings/EditCardsLayout.java b/app/src/main/java/me/edgan/redditslide/ui/settings/EditCardsLayout.java index e276b89f5..0b23397e6 100644 --- a/app/src/main/java/me/edgan/redditslide/ui/settings/EditCardsLayout.java +++ b/app/src/main/java/me/edgan/redditslide/ui/settings/EditCardsLayout.java @@ -576,9 +576,11 @@ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { // Smaller tags// ((TextView) findViewById(R.id.small_tag_current)) .setText( - SettingValues.smallTag + SettingValues.smallTag == 1 ? getString(R.string.show_top_right) - : getString(R.string.disabled)); + : SettingValues.smallTag == 2 + ? getString(R.string.show_bottom_right) + : getString(R.string.disabled)); findViewById(R.id.small_tag_layout) .setOnClickListener( @@ -594,7 +596,7 @@ public void onClick(View v) { public boolean onMenuItemClick(MenuItem item) { switch (item.getItemId()) { case R.id.disabled: - SettingValues.smallTag = false; + SettingValues.smallTag = 0; SettingValues.prefs .edit() .putBoolean( @@ -605,28 +607,41 @@ public boolean onMenuItemClick(MenuItem item) { .setText(R.string.small_tag_disabled); break; case R.id.show_top_right: - SettingValues.smallTag = true; + SettingValues.smallTag = 1; SettingValues.prefs .edit() - .putBoolean( + .putInt( SettingValues .PREF_SMALL_TAG, - true) + 1) .apply(); layout.removeAllViews(); layout.addView( CreateCardView.setSmallTag( - true, layout)); + 1, layout)); + break; + case R.id.show_bottom_right: + SettingValues.smallTag = 2; + SettingValues.prefs + .edit() + .putInt( + SettingValues + .PREF_SMALL_TAG, + 2) + .apply(); + layout.removeAllViews(); + layout.addView( + CreateCardView.setSmallTag( + 2, layout)); break; } ((TextView) findViewById(R.id.small_tag_current)) - .setText( - SettingValues.smallTag - ? getString( - R.string - .show_top_right) - : getString( - R.string.disabled)); + .setText( + SettingValues.smallTag == 1 + ? getString(R.string.show_top_right) + : SettingValues.smallTag == 2 + ? getString(R.string.show_bottom_right) + : getString(R.string.disabled)); return true; } }); diff --git a/app/src/main/res/layout/header_image_title_view.xml b/app/src/main/res/layout/header_image_title_view.xml index 83b79addd..87a141e1c 100644 --- a/app/src/main/res/layout/header_image_title_view.xml +++ b/app/src/main/res/layout/header_image_title_view.xml @@ -21,15 +21,16 @@ android:layout_width="wrap_content" android:text="IMAGE" android:id="@+id/tag" - android:layout_alignParentTop="true" - android:textAllCaps="true" + android:layout_alignBottom="@+id/leadimage" android:layout_alignParentRight="true" + android:textAllCaps="true" android:alpha=".86" android:layout_margin="10dp" android:textSize="10sp" android:textStyle="bold" android:padding="2dp" - android:layout_height="wrap_content" /> + android:layout_height="wrap_content" + android:gravity="bottom" /> + \ 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 5c5d88e7f..b44c90c57 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1088,6 +1088,7 @@ Big picture Press arrow to show actionbar Show top right + Show bottom right Disabled Long press for actionbar Always show actionbar From acc0142496cb892649e96c348c5dffea02c3dfca Mon Sep 17 00:00:00 2001 From: PrimaryVision <155949587+PrimaryVision@users.noreply.github.com> Date: Tue, 27 May 2025 23:49:06 +0530 Subject: [PATCH 80/99] change boolean to integer --- .../me/edgan/redditslide/ui/settings/EditCardsLayout.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/me/edgan/redditslide/ui/settings/EditCardsLayout.java b/app/src/main/java/me/edgan/redditslide/ui/settings/EditCardsLayout.java index 0b23397e6..64379d08a 100644 --- a/app/src/main/java/me/edgan/redditslide/ui/settings/EditCardsLayout.java +++ b/app/src/main/java/me/edgan/redditslide/ui/settings/EditCardsLayout.java @@ -599,9 +599,9 @@ public boolean onMenuItemClick(MenuItem item) { SettingValues.smallTag = 0; SettingValues.prefs .edit() - .putBoolean( + .putInt( SettingValues.PREF_SMALL_TAG, - false) + 0) .apply(); ((TextView) findViewById(R.id.small_tag_current)) .setText(R.string.small_tag_disabled); From 38980586c6a91221612204bb2ed8c9460dbe224c Mon Sep 17 00:00:00 2001 From: PrimaryVision <155949587+PrimaryVision@users.noreply.github.com> Date: Tue, 27 May 2025 23:55:13 +0530 Subject: [PATCH 81/99] fix small tag preview --- .../me/edgan/redditslide/ui/settings/EditCardsLayout.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/src/main/java/me/edgan/redditslide/ui/settings/EditCardsLayout.java b/app/src/main/java/me/edgan/redditslide/ui/settings/EditCardsLayout.java index 64379d08a..178f4d58f 100644 --- a/app/src/main/java/me/edgan/redditslide/ui/settings/EditCardsLayout.java +++ b/app/src/main/java/me/edgan/redditslide/ui/settings/EditCardsLayout.java @@ -605,6 +605,10 @@ public boolean onMenuItemClick(MenuItem item) { .apply(); ((TextView) findViewById(R.id.small_tag_current)) .setText(R.string.small_tag_disabled); + layout.removeAllViews(); + layout.addView( + CreateCardView.setSmallTag( + 0, layout)); break; case R.id.show_top_right: SettingValues.smallTag = 1; From 0d98fb400147ded2b6ae2ac3501183394998966b Mon Sep 17 00:00:00 2001 From: PrimaryVision <155949587+PrimaryVision@users.noreply.github.com> Date: Tue, 27 May 2025 23:58:25 +0530 Subject: [PATCH 82/99] Moved Smaller content tag setting --- .../layout/activity_settings_theme_card.xml | 60 ++++++++++--------- 1 file changed, 31 insertions(+), 29 deletions(-) diff --git a/app/src/main/res/layout/activity_settings_theme_card.xml b/app/src/main/res/layout/activity_settings_theme_card.xml index 15560fe33..09a4374c9 100644 --- a/app/src/main/res/layout/activity_settings_theme_card.xml +++ b/app/src/main/res/layout/activity_settings_theme_card.xml @@ -225,6 +225,37 @@ android:textColor="?attr/colorAccent" android:layout_height="wrap_content" /> + + + + + + + + + - - - - - - - From 71dbe9801046aa8b9fc399702cf239732fd9c607 Mon Sep 17 00:00:00 2001 From: PrimaryVision <155949587+PrimaryVision@users.noreply.github.com> Date: Wed, 28 May 2025 00:15:28 +0530 Subject: [PATCH 83/99] Update smallTag preference default to Disabled --- app/src/main/java/me/edgan/redditslide/SettingValues.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/me/edgan/redditslide/SettingValues.java b/app/src/main/java/me/edgan/redditslide/SettingValues.java index f6442c1af..9fa9bf4c2 100644 --- a/app/src/main/java/me/edgan/redditslide/SettingValues.java +++ b/app/src/main/java/me/edgan/redditslide/SettingValues.java @@ -431,7 +431,7 @@ public static void setAllValues(SharedPreferences settings) { if (prefs.contains(PREF_SMALL_TAG) && (prefs.getAll().get(PREF_SMALL_TAG) instanceof Boolean)) { prefs.edit().remove(PREF_SMALL_TAG).apply(); } - smallTag = prefs.getInt(PREF_SMALL_TAG, 1); + smallTag = prefs.getInt(PREF_SMALL_TAG, 0); swap = prefs.getBoolean(PREF_SWAP, false); hideSelftextLeadImage = prefs.getBoolean(PREF_SELFTEXT_IMAGE_COMMENT, false); image = prefs.getBoolean(PREF_IMAGE, true); From 6dd93b2e3f85dbba488272fa29c43d350a3d60dc Mon Sep 17 00:00:00 2001 From: PrimaryVision <155949587+PrimaryVision@users.noreply.github.com> Date: Wed, 28 May 2025 18:46:48 +0530 Subject: [PATCH 84/99] Renamed `PREF_SMALL_TAG` to `PREF_SMALL_TAG_DROPDOWN` --- .../main/java/me/edgan/redditslide/SettingValues.java | 9 +++++---- .../java/me/edgan/redditslide/Views/CreateCardView.java | 2 +- .../edgan/redditslide/ui/settings/EditCardsLayout.java | 6 +++--- 3 files changed, 9 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/me/edgan/redditslide/SettingValues.java b/app/src/main/java/me/edgan/redditslide/SettingValues.java index 9fa9bf4c2..4c2e43f87 100644 --- a/app/src/main/java/me/edgan/redditslide/SettingValues.java +++ b/app/src/main/java/me/edgan/redditslide/SettingValues.java @@ -45,7 +45,7 @@ public class SettingValues { public static final String PREF_EXPANDED_TOOLBAR = "expandedToolbar"; public static final String PREF_SWAP = "Swap"; public static final String PREF_ACTIONBAR_VISIBLE = "actionbarVisible"; - public static final String PREF_SMALL_TAG = "smallTag"; + public static final String PREF_SMALL_TAG_DROPDOWN = "smallTagDropdown"; public static final String PREF_ACTIONBAR_TAP = "actionbarTap"; public static final String PREF_STORE_HISTORY = "storehistory"; public static final String PREF_STORE_NSFW_HISTORY = "storensfw"; @@ -428,10 +428,11 @@ public static void setAllValues(SharedPreferences settings) { commentPager = prefs.getBoolean(PREF_COMMENT_PAGER, false); // Remove the old boolean preference for smallTag to prevent ClassCastException - if (prefs.contains(PREF_SMALL_TAG) && (prefs.getAll().get(PREF_SMALL_TAG) instanceof Boolean)) { - prefs.edit().remove(PREF_SMALL_TAG).apply(); + // Remove the old boolean preference for smallTag to prevent ClassCastException + if (prefs.contains("smallTag") && (prefs.getAll().get("smallTag") instanceof Boolean)) { + prefs.edit().remove("smallTag").apply(); } - smallTag = prefs.getInt(PREF_SMALL_TAG, 0); + smallTag = prefs.getInt(PREF_SMALL_TAG_DROPDOWN, 0); swap = prefs.getBoolean(PREF_SWAP, false); hideSelftextLeadImage = prefs.getBoolean(PREF_SELFTEXT_IMAGE_COMMENT, false); image = prefs.getBoolean(PREF_IMAGE, true); diff --git a/app/src/main/java/me/edgan/redditslide/Views/CreateCardView.java b/app/src/main/java/me/edgan/redditslide/Views/CreateCardView.java index 84ffe1397..c8410081e 100644 --- a/app/src/main/java/me/edgan/redditslide/Views/CreateCardView.java +++ b/app/src/main/java/me/edgan/redditslide/Views/CreateCardView.java @@ -205,7 +205,7 @@ public static View setActionbarVisible(boolean isChecked, ViewGroup parent) { public static View setSmallTag(int value, ViewGroup parent) { - SettingValues.prefs.edit().putInt(SettingValues.PREF_SMALL_TAG, value).apply(); + SettingValues.prefs.edit().putInt(SettingValues.PREF_SMALL_TAG_DROPDOWN, value).apply(); SettingValues.smallTag = value; return CreateView(parent); } diff --git a/app/src/main/java/me/edgan/redditslide/ui/settings/EditCardsLayout.java b/app/src/main/java/me/edgan/redditslide/ui/settings/EditCardsLayout.java index 178f4d58f..398d6b4a5 100644 --- a/app/src/main/java/me/edgan/redditslide/ui/settings/EditCardsLayout.java +++ b/app/src/main/java/me/edgan/redditslide/ui/settings/EditCardsLayout.java @@ -600,7 +600,7 @@ public boolean onMenuItemClick(MenuItem item) { SettingValues.prefs .edit() .putInt( - SettingValues.PREF_SMALL_TAG, + SettingValues.PREF_SMALL_TAG_DROPDOWN, 0) .apply(); ((TextView) findViewById(R.id.small_tag_current)) @@ -616,7 +616,7 @@ public boolean onMenuItemClick(MenuItem item) { .edit() .putInt( SettingValues - .PREF_SMALL_TAG, + .PREF_SMALL_TAG_DROPDOWN, 1) .apply(); layout.removeAllViews(); @@ -630,7 +630,7 @@ public boolean onMenuItemClick(MenuItem item) { .edit() .putInt( SettingValues - .PREF_SMALL_TAG, + .PREF_SMALL_TAG_DROPDOWN, 2) .apply(); layout.removeAllViews(); From 649e9289acf6db1260968310545fcaac8c072aec Mon Sep 17 00:00:00 2001 From: Nathan Grennan Date: Wed, 28 May 2025 16:18:06 -0700 Subject: [PATCH 85/99] Fixed subbredit filter prefix matching to use startswith --- app/src/main/java/me/edgan/redditslide/PostMatch.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/me/edgan/redditslide/PostMatch.java b/app/src/main/java/me/edgan/redditslide/PostMatch.java index 841c455c5..f39862081 100644 --- a/app/src/main/java/me/edgan/redditslide/PostMatch.java +++ b/app/src/main/java/me/edgan/redditslide/PostMatch.java @@ -120,7 +120,7 @@ public static boolean doesMatch(Submission s, String baseSubreddit, boolean igno if (SettingValues.subredditFilterPrefixMatching && subreddit.length() >= 6) { String lowerSubreddit = subreddit.toLowerCase(); if (SettingValues.subredditFilters.stream() - .anyMatch(filter -> filter.contains(lowerSubreddit))) { + .anyMatch(filter -> lowerSubreddit.startsWith(filter))) { return true; } } else { From 784624dcdeebb81971db5aca3ea0701926cf7ff5 Mon Sep 17 00:00:00 2001 From: Nathan Grennan Date: Wed, 28 May 2025 18:43:55 -0700 Subject: [PATCH 86/99] Added discoverium.yml --- discoverium.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 discoverium.yml diff --git a/discoverium.yml b/discoverium.yml new file mode 100644 index 000000000..e46229303 --- /dev/null +++ b/discoverium.yml @@ -0,0 +1,5 @@ +app: + name: Slide + description: Slide is an open-source, ad-free Reddit browser for Android. It is based around the Java Reddit API Wrapper. + releases: + url: https://github.com/cygnusx-1-org/Slide/releases From 9db68002060846c4b47cd3e969c65c4650b9c976 Mon Sep 17 00:00:00 2001 From: Nathan Grennan Date: Thu, 29 May 2025 01:31:08 -0700 Subject: [PATCH 87/99] Adding icon to discoverium.yml --- discoverium.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/discoverium.yml b/discoverium.yml index e46229303..0da3f5c47 100644 --- a/discoverium.yml +++ b/discoverium.yml @@ -1,5 +1,6 @@ app: name: Slide description: Slide is an open-source, ad-free Reddit browser for Android. It is based around the Java Reddit API Wrapper. + icon: https://raw.githubusercontent.com/cygnusx-1-org/Slide/refs/heads/master/app/src/main/ic_launcher-web.png releases: url: https://github.com/cygnusx-1-org/Slide/releases From aae24c5e0e21cbd76ed975becd20ed6fdec881c2 Mon Sep 17 00:00:00 2001 From: Nathan Grennan Date: Fri, 30 May 2025 15:44:00 -0700 Subject: [PATCH 88/99] Fixed reddit.com/comments links --- .../main/java/me/edgan/redditslide/OpenRedditLink.java | 8 -------- 1 file changed, 8 deletions(-) diff --git a/app/src/main/java/me/edgan/redditslide/OpenRedditLink.java b/app/src/main/java/me/edgan/redditslide/OpenRedditLink.java index 520f4969f..a5198e199 100644 --- a/app/src/main/java/me/edgan/redditslide/OpenRedditLink.java +++ b/app/src/main/java/me/edgan/redditslide/OpenRedditLink.java @@ -450,14 +450,6 @@ public static RedditLinkType getRedditLinkType(@NonNull Uri uri) { return RedditLinkType.SHORTENED; } - // Handle cases like reddit.com/comments/xxxx as shortened links - if (host.equals("reddit.com") && path.matches("(?i)/comments/[^/]+/?")) { - boolean isSubmissionOrComment = path.matches("(?i)/(?:r|u(?:ser)?)/[^/]+/comments/.*"); - if (!isSubmissionOrComment) { - return RedditLinkType.SHORTENED; - } - } - if (path.matches("(?i)/live/[^/]*")) { return RedditLinkType.LIVE; } else if (path.matches("(?i)/message/compose.*")) { From cd4f241af2fd449ffe3377a8ecf388015ce68b8c Mon Sep 17 00:00:00 2001 From: Nathan Grennan Date: Fri, 30 May 2025 19:03:27 -0700 Subject: [PATCH 89/99] Updated versionCode in app/build.gradle Updated CHANGELOG.md --- CHANGELOG.md | 7 +++++++ app/build.gradle | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7017ac41d..25e74284f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,13 @@ The old changelog can be read in the [CHANGELOG.md](https://github.com/Haptic-Ap --- +7.3.3 / 2025-5-30 +============ +* Fixed reddit.com/comments links +* Added ability to put the small content tag in the top right corner +* Fixed off-by-one bugs in Reddit Gallery in horizontal and verticals modes +* Fixed various crashes + 7.3.2 / 2025-5-24 ============ * Fixed the cascade effect while using oldSwipeMode diff --git a/app/build.gradle b/app/build.gradle index 785c58e83..a00f2863a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -49,7 +49,7 @@ android { multiDexEnabled true targetSdk 34 vectorDrawables.useSupportLibrary true - versionCode 732 + versionCode 733 versionName androidGitVersion.name() } From 43933c97007878774f4b9d7eaa3391d4e9699a9e Mon Sep 17 00:00:00 2001 From: Nathan Grennan Date: Fri, 30 May 2025 19:56:26 -0700 Subject: [PATCH 90/99] Added category and author to discoverium.yml --- discoverium.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/discoverium.yml b/discoverium.yml index 0da3f5c47..08422f1e5 100644 --- a/discoverium.yml +++ b/discoverium.yml @@ -1,5 +1,7 @@ app: name: Slide + author: Nathan Grennan + category: social description: Slide is an open-source, ad-free Reddit browser for Android. It is based around the Java Reddit API Wrapper. icon: https://raw.githubusercontent.com/cygnusx-1-org/Slide/refs/heads/master/app/src/main/ic_launcher-web.png releases: From c7a6fca3884db88e8dfc4bc97d92f72cfc058519 Mon Sep 17 00:00:00 2001 From: Nathan Grennan Date: Fri, 30 May 2025 19:58:27 -0700 Subject: [PATCH 91/99] Changed author to authors in discoverium.yml --- discoverium.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discoverium.yml b/discoverium.yml index 08422f1e5..eee7e7289 100644 --- a/discoverium.yml +++ b/discoverium.yml @@ -1,6 +1,6 @@ app: name: Slide - author: Nathan Grennan + authors: Nathan Grennan category: social description: Slide is an open-source, ad-free Reddit browser for Android. It is based around the Java Reddit API Wrapper. icon: https://raw.githubusercontent.com/cygnusx-1-org/Slide/refs/heads/master/app/src/main/ic_launcher-web.png From e7a4459e9ad0cdcc944b1e3db6849c9f5135d681 Mon Sep 17 00:00:00 2001 From: Nathan Grennan Date: Fri, 30 May 2025 22:19:21 -0700 Subject: [PATCH 92/99] Fixed the logo url in discoverium.yml --- discoverium.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/discoverium.yml b/discoverium.yml index eee7e7289..d5422ec82 100644 --- a/discoverium.yml +++ b/discoverium.yml @@ -3,6 +3,6 @@ app: authors: Nathan Grennan category: social description: Slide is an open-source, ad-free Reddit browser for Android. It is based around the Java Reddit API Wrapper. - icon: https://raw.githubusercontent.com/cygnusx-1-org/Slide/refs/heads/master/app/src/main/ic_launcher-web.png + icon: https://raw.githubusercontent.com/cygnusx-1-org/Slide/refs/heads/master/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png releases: url: https://github.com/cygnusx-1-org/Slide/releases From e907315e1f14a8dca3f1dba80ab75115f1d4b114 Mon Sep 17 00:00:00 2001 From: Nathan Grennan Date: Sat, 31 May 2025 23:05:50 -0700 Subject: [PATCH 93/99] Fixed Sorting problem. #254 --- .../me/edgan/redditslide/Fragments/CommentPage.java | 12 ++++++++++-- .../java/me/edgan/redditslide/SettingValues.java | 4 ++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/me/edgan/redditslide/Fragments/CommentPage.java b/app/src/main/java/me/edgan/redditslide/Fragments/CommentPage.java index 0dba09a9c..0cf767ed0 100644 --- a/app/src/main/java/me/edgan/redditslide/Fragments/CommentPage.java +++ b/app/src/main/java/me/edgan/redditslide/Fragments/CommentPage.java @@ -2004,12 +2004,16 @@ && getActivity() instanceof CommentsScreen Submission s = ((CommentsScreen) getActivity()).currentPosts.get(page); if (s != null && s.getDataNode().has("suggested_sort") - && !s.getDataNode().get("suggested_sort").asText().equalsIgnoreCase("null")) { + && !s.getDataNode().get("suggested_sort").asText().equalsIgnoreCase("null") + && !SettingValues.hasCommentSort(s.getSubredditName())) { + // Only use suggested sort if user hasn't set a custom preference String sorting = s.getDataNode().get("suggested_sort").asText().toUpperCase(); sorting = sorting.replace("İ", "I"); commentSorting = CommentSort.valueOf(sorting); + Log.d("CommentPage", "Using suggested sort: " + commentSorting.name() + " because no custom preference exists"); } else if (s != null) { commentSorting = SettingValues.getCommentSorting(s.getSubredditName()); + Log.d("CommentPage", "Using saved comment sort preference: " + commentSorting.name()); } if (load) comments.setSorting(commentSorting); if (adapter == null) { @@ -2025,12 +2029,16 @@ && getActivity() instanceof CommentsScreen && !s.getDataNode() .get("suggested_sort") .asText() - .equalsIgnoreCase("null")) { + .equalsIgnoreCase("null") + && !SettingValues.hasCommentSort(s.getSubredditName())) { + // Only use suggested sort if user hasn't set a custom preference String sorting = s.getDataNode().get("suggested_sort").asText().toUpperCase(); sorting = sorting.replace("İ", "I"); commentSorting = CommentSort.valueOf(sorting); + Log.d("CommentPage", "Using suggested sort: " + commentSorting.name() + " because no custom preference exists"); } else if (s != null) { commentSorting = SettingValues.getCommentSorting(s.getSubredditName()); + Log.d("CommentPage", "Using saved comment sort preference: " + commentSorting.name()); } if (load) comments.setSorting(commentSorting); if (adapter == null) { diff --git a/app/src/main/java/me/edgan/redditslide/SettingValues.java b/app/src/main/java/me/edgan/redditslide/SettingValues.java index 4c2e43f87..4a60b035d 100644 --- a/app/src/main/java/me/edgan/redditslide/SettingValues.java +++ b/app/src/main/java/me/edgan/redditslide/SettingValues.java @@ -667,6 +667,10 @@ public static boolean hasSort(String subreddit) { return prefs.contains("defaultSort" + subreddit.toLowerCase(Locale.ENGLISH)); } + public static boolean hasCommentSort(String subreddit) { + return prefs.contains("defaultComment" + subreddit.toLowerCase(Locale.ENGLISH)); + } + public enum RemovalReasonType { SLIDE, TOOLBOX, From d87edf46217d2a09b03802a23f2cf2712286ae0e Mon Sep 17 00:00:00 2001 From: Nathan Grennan Date: Tue, 3 Jun 2025 15:50:42 -0700 Subject: [PATCH 94/99] Switched out Obtainium for Discoverium in README.md --- README.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index fb1f115de..38777cef9 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,8 @@ Android. It is based around the [Java Reddit API Wrapper](https://github.com/mattbdean/JRAW). Slide is available here on [GitHub](https://github.com/) in -[releases](https://github.com/edgan/Slide/releases), and [Google Play Store](https://play.google.com/store/apps/details?id=me.edgan.redditslide&hl=en_US). +[releases](https://github.com/edgan/Slide/releases), and +[Google Play Store](https://play.google.com/store/apps/details?id=me.edgan.redditslide&hl=en_US).
@@ -39,10 +40,10 @@ See [SETUP.md](/docs/SETUP.md) See [DEBUGGING.md](/docs/DEBUGGING.md) # Updates -The easiest way, for now, to get updates is -[Obtainium](https://github.com/ImranR98/Obtainium). -[Obtainium](https://github.com/ImranR98/Obtainium) can also be found on -[F-Droid](https://f-droid.org/). +The easiest way to get updates, outside the +[Google Play Store](https://play.google.com/store/apps/details?id=me.edgan.redditslide&hl=en_US), +is [Discoverium](https://github.com/cygnusx-1-org/Discoverium/). It has a search +function, and lets you add apps to the list for keeping the apps updated. # Issues In any project it's likely that a few bugs will slip through the cracks, so it From fbb6149206044df8738b4c0d68c6da715f829de5 Mon Sep 17 00:00:00 2001 From: Nathan Grennan Date: Tue, 3 Jun 2025 23:34:40 -0700 Subject: [PATCH 95/99] Replaced Updates section with an Installation section in README.md --- README.md | 32 ++++++++++++++++++++++++-------- assets/badges/discoverium.png | Bin 0 -> 34603 bytes assets/badges/github.png | Bin 0 -> 6930 bytes 3 files changed, 24 insertions(+), 8 deletions(-) create mode 100644 assets/badges/discoverium.png create mode 100644 assets/badges/github.png diff --git a/README.md b/README.md index 38777cef9..0b34ce973 100644 --- a/README.md +++ b/README.md @@ -30,8 +30,30 @@ Slide is available here on [GitHub](https://github.com/) in
-# Current project status -This project is back with this fork, and is being **maintained**. +# Installation +The easiest way to install Slide or get updates for Slide, outside the +[Google Play Store](https://play.google.com/store/apps/details?id=me.edgan.redditslide&hl=en_US), +is [Discoverium](https://github.com/cygnusx-1-org/Discoverium/). It has a search +function, and lets you add apps to the list for keeping the apps updated. +

+ + Get it on Google Play + + + + Get it on Discoverium + + +     + + + + Get it on Github + + +

# Setup See [SETUP.md](/docs/SETUP.md) @@ -39,12 +61,6 @@ See [SETUP.md](/docs/SETUP.md) # Android device debugging via USB See [DEBUGGING.md](/docs/DEBUGGING.md) -# Updates -The easiest way to get updates, outside the -[Google Play Store](https://play.google.com/store/apps/details?id=me.edgan.redditslide&hl=en_US), -is [Discoverium](https://github.com/cygnusx-1-org/Discoverium/). It has a search -function, and lets you add apps to the list for keeping the apps updated. - # Issues In any project it's likely that a few bugs will slip through the cracks, so it helps greatly if people document any bugs they find to ensure that they get diff --git a/assets/badges/discoverium.png b/assets/badges/discoverium.png new file mode 100644 index 0000000000000000000000000000000000000000..ebe152ceccbaeb11d7088644b636ab218739a2b3 GIT binary patch literal 34603 zcmcG#g;!hM^F55VSaEk++}*WkaCdjNKyfKhTw2_n;10#DSb+e=-5rX%zoF0P`}+^x ztaS-DIqT%!xifR-?7b&KSyB1}5+M>46x0WRjD#u_6pTFN|89hLkiRz(f&j=HoRye@ z7!*`(9P*P1Jmef2s4D##dSQq$0CGZZCZnnV1?5c*1r-=C~zMN%AFMo>d+Vp zia!+!3eP#SRYef;1){Tz4iE|o8SCwUhDy)GhkW?n4WJsWgA9$%hb_V9d;a~wM@nbL(@m77) z(f@pRK0oKM;`-88A}q4#au@p8K>?sEoCgd`+>7EVs5*_E1h z*}$qnAEUCe?UYadE&%M72?Uj8q5tZeo}NBDKR?%de0=O`+=>Wk>2acX8*IMJ@=Dt~wjKY210?=eNe96F;A4z7) z@VQ;_FlsjL3F5(%RM}49{_i)Z#56Q!4rj_=EjE6AI~CXn!6IF?gq!+?XXt4g5b)|H z1bN+CfdAR&Ql6C!^5W-zNICTy_embJ?P;)IsC#)&>bX90xi>DdIZ<`n4U6VHE_7St zeQgT@fi4&_|Jim|#inTqoF$M#;<5Y#a%n<`?sFA7m&f0b@m{KVIt~kM8 zZMgICWCW2Wvzxi?Z{5P2l(aONeuMQyd_gR~?Myv$YBH1FY@7G>AyH^(=$9;i!I17c zi;=sBxvto;4I~H+g^_yi zBWFYw)z-3&@$C_J-ZWNQ)F%FC85>EB`$Mg+;g-GZk@@-g=;{xss3$)iS6W9rxf$eh zp0;V?h)78d2js)}t#bUh88jcbBCJ|} zy*(Ji(`T!BN{PcDWai(dbpPFO{jW$IhUWDkWY&6xr7t-;Kkao+Iha#V+AqgA-oJbI zF3sj;xAmxMq+0WTKM`|O)fQXre_hTIdwqM0w&HiYVkKA*bdVp#OfM}ZwaW864|x*5 zakTV*7!~~&O5^-m`Y!Nj!guS@X5!-Fr(OH`QNvd9tTAzMWOH+KJXR02EPe-aZv0tK zNEZKG*K78`jsIz>>v<{K%+JpcNKH*0>oov3)z{ben3h&DH12;>|8)3Ykfw1x%>_K; z+09m%B;=1j=3w-gTh>9`dQVB@CL!l31pTxBf0GV@2|pu1%qNoOV&4-4|Co^`cpP*9 zF~)pXQ&SVy^L%x{^U+h;|7La92aa-a2%jDwI~X|kz(0yW#(sr(_9*Dk;r?Rh9_POt z!Ex05e6z%J6y#85k&W9^SoU7w)put&Y2k0yzX72Owl9V)t~;PGG~Y$|gm8PF=gWVt z`@a~kuu!WswH)_?dWp*roE9$3ed z-UFHi{~Kyw03%yxJpA98SL9I8e~(t48X5GzRK}S4zpVEEhW)=mmbhw9N`9M{OH8QC zUTlNJPreV2vR@2%kxQ@=f%w;LUrazzu4170xveMA#0cF!jS>T}`q^Dn9WXeiFYt8qNv zQ+X(Ou`9_kccG!h*--i>Ti&CT@WZ23u69SZ&7L)qy$0Di>h$=bRljC=tw@G^iscQO z>w38+{xWaEsM`<@kBrO8&p!;wQ7tE}M|VAF{@q0>hQsMxcBGdtWBf;qH%m@kG`_+f z%P#N9p4Q((H{WeW5#Zx{4rd?n?Pb<4Ob$t#R(ITNI=$>VSrQ8zH%v7{d>V@P`D)6m z2ifo}F>49py**toM_t0DkGy|tdU;>&w!9!&2MkFHzs}BB?dGa>Vg*jt--a|lU5wms zpanE*++NQ%btVJ&ab2MouHrp^2g z1DL~2iH#y3qdNY0{?ccfrL^gQLME9K1p{DX6>}yJ^-|cg9(|t1!>V!1IX^KdND8+K z_g+r+cM^%je}i;ij1&8^{?k@6xm%l_2=PPcJ4J>-2;sW`1)pgbY5oZ zxhuZq46l_V!#UrRj`08kb^=#9$BgYbR8L_MpEUrZ(2I6E_8RqyKu1p`oVBk-MnCAhW&L3}f)Weq3%WrB*iSyn5|e*_;twN-m2?IIkw4xdS}sZ91QEW%h3-32{7i4033{Fu#@0o<^3 z;@XIkq|j>o@xUk-!=Q9BQ4?H1Oh{*~a6MEc@WCQ1TQZg>%M+c&dqUFpC8uj7jO8W8Vb^Ls{8Oy)LY)P6KbY*6 z%Wn28nCvOg=d>fGvs3s6Qf{CMUH#eRg7(~DFd8ZrdA3m$JeTRs+_W@ErxNK zD(6oTb<9_)5h+*6#Sp&2*57BctJ*dSjS844-bahX0nIZ5RXiz59g_7G04fpF>_Y|A zF^8BDNmYwLY8(R=}*_6 zq`%XE*GE1`R!g7jphaTtxKh1Y^*OD)p@J5Kl!1`X$JTqvUYuX-ehhurb5+WExbUmr zjW`zI1Y510y~`HS-W0%5&rLGUf+-~y#%J#1PS*V@i%}GNS%MHsgC1AT(Duq7wyCU@ z>p898S`}pkWM0*}_>{(=OxKfi-kBvgNZbsA6ZhaIaEhp?+9=ok=#~LhWn zcH-s6wwhu8WS4MEUiPZaGwch*1aXkc)McPHK}k3*&hM|8UPU(~1$`j)+WxT|@cPUV zE0PrO#1WUnF)o+@DOw$G__JLG2r@J}XR8cGj9#yefRg~4QEk*t;~tnX!AtPq^E}zZ zpRjqm>W+)3??dK2SL&wqn}FvHMW3?;5l{S68PGIH{+kK#y_Eq^}Y+6_$p>^3`Mj*`0)6H5BUAX-&|3o$_aIHD!4&c zrX1cPqG737Gs4cR&`n0}`_JA+_&NGq15pA0m`Sm6(M9-KHDjTmpE$ax-bfWqqPvMQ zM#v9(e~gTQNc${PdraoJ?|G)fP;Mzc0Vj*dq~19L-@9DCa{-3A^!EP#gU-$w@&VQO z8CJ%L1si^sL{|dX3Y07}_X0P4|3x7gk$_tj5TCGm{rEKMv&!Z(QY_iiwt8JfFxYur z>2%&Q?suZ%)CAcNj0xQ?6AT(&rq8kR@}{n_Wy3=XPHrEPuDgDgiv>uHr{Z)w{`xeo z?|;9;5!-QOaO%CB5Ptk60}{Do)-#rG8UJKR`P;eGGh&DFiRqZgQ{&%1b3F>~tYnfu zu*6jQ_@?390~u<(#I3Co&(>l0huweAdq>8a4o#UWx)`!rxwKZqqMfa`Pa9OvuVD80 z+J0+soDKwHcC2K3`jpb_QYuuq`zi58Rm+yK+nAJYVvSQNuHW&IX)VOHEgjQ{vSQ1eQZEyONn-Ipv?8G5XUJ3q>wIgAE7wB&u|_pcw?FSoUT zmlpy$-u*n5D`>ZGDzPcZ-^~JXz|*t{*%BnPbo_;EKsd^f7__Rur}*PA(xhJ7 z(c@072cW@d#FCct0iu@s-HZjuz%*gFGwq;*yfD&Dfwg;7C!eLIbhEuI57O6jvezDQ zG@(SJMETI=_$3gZ!w^Qh?+Hv{ddL{%Y-tT$FF!heZjxyh63L3_-iwd(?V3W{R9;P8 zn&RbdgzA7KmnrHLZH*pnDhFy7k6|6DH^V$$BT&`$YRiZ*I_Gk8_Vu}PnKIU z177a$;fKuYj2CabdSeBq1ZttF`L*987$-p}LE-4c4EQu+rMKkKC(SC!Z{M(t2kGTt$ZLUeC}C&)S8oX2yGi z;E^qTUzXNhZfpX;b&LcA1Pf(ys65O35DcJ+AZN{Wgh?>%J6PfL^oKU294OBELYSe5 zES#1*DNil#I3%=0-1sc~ciFag+7qm%n#=CY+_+9(!VlL)I&S_q2mr09f-w*o6tVYL z*3OxEath?Wmep|D6+F}l?YXjq&wHk9?ujoQp%XSX9HxDna60L8&0{^4=jy=y*A4x6 z&F4e!l0#er@k}4@mdEn&K#pHQ~80BG(3#yHvalkNJ?Q zqgo{DcmDKT$Ek=S%5&@0lYx(gi7qr?@YhJvqTQdgJ@80azV0GwMD)6_kAYWxM^t&# z*r{&lc5JVF*&<#eMECt+BHVWV)KXGX^KRaU2fK~ce#e@X>d^BLu%H#T%bZt1nrzRH(!?$jz`r>!2RYhx=7qAwzW8$6l6|0s?Z(aW_X zqP;HR)wDSV7rcgarnE=OiuS35i&qpbpR|DedY81P4#oXF2@=9cN0+rokUO5wTQxWK z8upE9h=j&{ppRZ0=)}wYW1CGa0f`d&}8QMG#J6XBH9#BC?H? z(viv{wO430_aSMJghce2yOahqA*Kdm_1h3MjU5p36FKG0bfJkom{wtNJZm0;csF+* zN6T^|Iy*Z@VNl5c+9bYbJ#k(EP0Oyxx642!|2a=wBk^??p3_4EnG;w|Cof!&#Ii12$993(WtUz!bNN(LHAaSmI=(&9}dpdt{Jje%*^wSIJLebA+vbJ zBl-1blLSF>==Vmo<<7O1*`i#IBu3py_IsV%b-%yOdzaQ9Zd6fNj3KjecpWfdvxk<% z%}`Cy^60ZCG1xfiql`uS0J)Ixb5xrZOi#z%cQBf1o56(Q9tNqHAgxM!-Fy#&nn~JR z6!X>tgOS7oY^}RiTPl+$;X*4OFnkRzHA!Ymv+%n?Q~|V$$Y9j<$FA1Ug*s~wJKps` z*q_5FFDD*dZ)@?cM~u!Lp>XoP7yGTxQDu`W1k(big5P%5cPRn)DV5wgR&N_#OZc`A zU^5kHegbF08s%cU_tS~%iC+l(M5$u~H>OkPU6g+Mtek5rvEU|-sfX1uI*VHQ21Q(( zxiPndlvj1cWSq2y@pW|sciINwgmooivD`COHGSxKt{|HKkwo@j=lHa9?Uwv^oNMpp z6YTBs2-V#$LMho{)egEg&DPXTU-HS_o1z9xK%wPYwat|7lVgTc z+ba20$~qo2$V1AG=@R$`YiiztKU#lN#nhB7xlau4OvVcKQYtGvC+Bko{dSfot1dnU ze}!KZik&0%KZ5{_8O)=8)L(wB_+F0cyADz5xvvO1T+iE=(l^^g#a7NEH`8rx#j(Kb zn2|l=50V84KK`voL+`M~GjPLT@mUyAo_YHG&xeSJ`I}*dvJlN}BLfGLGYWP5)XbHg zC7%P5NS#RB%8c*P0*0P$<=KjuI$r`r276e91G8>KIDckVh%3E9`6U$mX7LA8kc2IBw&T{(!vZn!g<$%#YC^t{D0ugk>D9Mv&OF6GW)5GOl znwKtRs(=e^fc<-1cE!}@xH>C*ISsaY^et3uFadJYQ4W^~-qBpO)s&x=5>M007b3F9 z`N0c;5w*VkeOihTf_L|WkNl3_&qG5)gB?VUwziQXkM$y3W#v`2WoliwK3#)DG&z~& zv1#(S@=K`5yH{J~bov@pE4itj)4679S3e(FOKY~_U6BgxxK)>%VjH_!Vycbq4c>L# zaxK2kSxe3S7WuM)J4PhXxCgwgyk))8g4i$^)xx}5=tn2FB13Y(Ya3(x?H66-cM}vQ zIYM8G>K#dL=XsIWZZsoTG2x|nss>IvlJb>N6v|Z?BS~T+r8BwJTrF!c%`HpQIUVLQ zT08|PS2$ONEd^)jRNX#oDm2n1fk1wKt*;TIx&*Z!sY;7B-wB$`}*n#gr+rrAOA4VWHt^67_0aNLKp1LpR}F6 zG@qC~I8Y2o3S#(Q6_A~$jr&?k9p1g6#af*gN&n|Vo^bTNWeswpP{?pPWV9%Oq6}D5L8oxz)V!TOmX5qp;O~>^QCUP?`9_+stAqQ)7uah9Z%m z1USTa!MD^g>S0uPZE1DK!x~iJVuG5Tj=@sx^D6ta5Pi+XEzaQ}#yArfG3(bZm5XC9 zQSP1!j=*86=e@UzOrKbr%Z7Li1oVN0ARqRZ_YhEC4#IC(9;cq4yJ%KupC|fQu`7L; zgTUP=6ZJOCQ)ac0oxIZx@Lb~lt!!42Ch5%LsiRC)Mru+DlsKo(j(#8x0~QtHtqTvbiC+*1aB5UY-4Za)zb)PgrqiuElk^F zbS>_Gxk6TZ66WD4e`^HvDAhOZd|U>(gZ3gtT$!9XJUKPs1fd0BpE7S6-_RjJA%J`9#sK&i z^lqV~3Jf;|Q~hqn8tJ0KS(8HdtA;W3kAXbdnt>kE>S`rnznT(G>l4%EKUDeVxV1I(gMod7?@9Vpd9NkL8_ zT7r<*t8RsKJ?r3S5)Rh~@BK~>yyXA=OIx3fHT6_Gm03d^6ZD;Pg?2# z=+$by3TD?Ost%wnMyjA4KpK!CPZdyPH4*%=TBo);MOJN;+2 z19L8?u&4boA|W+8HEY(dGSm@e44&iS=AP36R z7@zRj*Gjo$k?UzStGk@Cvdu__Do@ot;DSZU`KXgWaG>`7a&HfhK4B=x<_7Dts6cL# zGJHQhl{$`Eo@qXml?E8=BgizR$jmsmaK#RRq_9j?62vXQK+P6ha-k-F(vIBw`1jY? z5?ZZiu5sH3G}qe?T`)1BwgiNING8uGa@w93k91PI!T26)E<7RvulBL{h*zN#w8MQg zIM|2t1*w&ZaB=XxWwgnZg+C2y)V<;zVLbPf3)2yK6w3AE^D%MsomXZUENf?I&Rqa7 zjFti|-odP<@i%+@+!PWUGP)C=QzgofkkwKZO&7%)g0*P~yR8tj6sYA@GHoBHy=ORDtM0-O`Jf+pd6tS_q00uqH1df}zN_gFP@Xa1jL| z7J3?Da8hfgJ5?FsIBC^1jPnn3F_+XQek*X`;NI3ED&lCwEXZx|uQTOX#4P+fuDIvS3Qrsr36N4Q!cwa*9f|iEX`fYVY(Y!m^=Z#~^qIiSJa^ zs%m_b=mk)pG4*Azc+k5dYAtMZSqK#6!V&_8CJg#baa(67)#PD8(4SK^aHs<>$3+6_ zYlgZW+q$;?){lc$R_oTvRva&X#Q9Qo34lLB_<;(7)q2hHen_&!S-k@BoXbQUX&HL$ zM6Ztw4FTT1FW&ZraD%Z=9PvZn71ox>b3b> zD3nE5mFLLXZs0bPRT!z*#c!*@hQGIA8FZRGrM+|N#nWGSPP(4Ce+Fy;SnKfja}j{= z9y}ip$YA($nL-J0~{19@etI$OAg$RYj7+yK@Emcrmz+xJG;0*T~*EEJ{8 zH@kdt@*2^*TfOa+)=7@ZdjxZgZo0UjC=DJrs5KbszErK){he6Z%u(a6qydRHe5Q5IOyzw4qVIp9Uu15n1 zok_7fnlZQ$M%LL6-}bi8YFhO^QllSOdzfu5qYH=+=hrePp*6(0k_iaXQP9mI6$iZ(*mFm2hB2%7j zVvhG6Gc>=nN6p_)j+1XHBf%9p%YMhD4!VQULhNK@1M)I#^37S=17<)CWg**5lQ!8@ zpD)NGAe>Z^v5M$H!xIR7L=3d9FJrTD;H9OpT&u@2!G+iLK40OBr2!Cg^UA<}rf7&C z#R~d^3yaZ}=C^I9`0XF*eiZ&9FXwT5H}ht12Cxdza_oA(dAiSey*GRHEOi|qusVb6 zxMC`G?tHd?NIPD)R2p{hcOg4ac(R2eHx2qYD<59)Q=Vrm`@;oje zvzQHa_|Al=X9U#$~vI62QWNtsTcYliNujvI`xP?#aD?&EJOp!D}MoA<3> z1!;Rs4AEfAmKD#(TgUY=v}ciuu1iH!&n-6anwK=~R>`w@aqno?6OM<`-7A~xv#X|& z2a!;lK*{ZV8h5$OboAIXew_=6Btojh*Nx4O&9uqCuAU09ap^*BJqnHb5HvVT+W3u1 zVHor;)ToeuL}}1+#E{Xb&_Pv2nvV9*>R0d{fE9J?I3qAPl2sc4T*kT-%!F%){u*y({|JD{|BQ6)}iP<8UBiLh7pbe`&C4ly;fU~}t*Iv=g-3@ue)xw=bSW?$j? zfRLjm+JH)NxL*42sFh*L0mewmu&84I^28daT+2o%k>^B|VV|AhCE*(#Vt5{@RRhs5 z)Y{}O+g`+*=Nw zMY69ccvjr7vJ}G(^7535@1Ad(x<(-o$(PlqU7kxwwcyNAJ;~yKj@$`>S>M9h$>p9| zk2P5-;01!PEgxR;U4l8hAU0Qm=sluwO(DELWVu8Hfku)qg22icpLL&2u4Q~XltyKK z4LOf(`AI6F*`E^od>Ad7A~1u{bES1*sM*ipo;d1F3EkzEzg$m^GwkU3jPcs7vB@w#3(g88EWei^fy=KN{NLwPrSdVjf5# z%3vy91KEpZXxb)yNG1ksv8i-(JZN7K1b4Qv>^dhzgr3kmCRO%v3)of z-Xw9OxpuqJOa%?n>U-@Zy&X_*l9LwTo~dPpev50pJAGXLY{OMH5@a3pe4~CFR$jU0 z^e+ofUYQ`NljDIuHZHE-?Lak|*+5nW+a9uW0jF_2toN0qW1-| zx!K!c3GjNci+@{9nGmSM(sN5?&2WJo3i{a4*EbE8R=Vxsuto@-R96qAp(8MA1qCF9 zMTtPcEX0C2BA~b2mt`B~rDI+)Fn))V5dKxelC?>L{DJty{Lx3=f>fR`p_`?oRk2=; ztqGpO^#%#Q^i7YIYnn!rrT8v@y>4`2-+2I6KVMu6=O`|ARtaq+yCqLVR1;dG79G42 z|9dfv^IBUAx>+bZ_qaazRs)QbkncA1lmzIi7IclTe-`Oh%_VDNjwuZ4El1UKONY)X z7+)PsX3Ha=XhIs>Jnz|=T;Z(#^*%JTMDmtue3~KO+qXNs zt~D)e42`H9qy_6EF}s$_82!1f-|-3ut&d5rTP^Bxo}EdBxPSQU zidqXp4ZHcFeIOk6I<;EQ&sb3f+0&Q!!&RtCk!_0InBJif@H*4zQ)E1ndmvw?EVzQ* z8DP7!arjS7(!;5TERT7cCHbm~P0$n072isd5?|RSS&IM8=Hn1N{O*=Nk4vfbrWuo>B*2oJdO@>XoG6yh#T27sH1z=NX|>L zbI8z^J;wBv#Vb7D4MPjLu$AZi=C9~J2RVkl^dAAGI9lnha#21rfyorA8}$7H#M)?A zpuH(Lb9zipCPK}=$NA7i-4MVh(IYVZ)OYN(!qa-LUbl1g$q+*(aVm!Y#5X;h42a3m zYrCq(<`x2tk5Fzzp7WH=a65 zp@}Qy=YVXJhPnHI@*M1H2VVKqPlwPz_JRz)#2auL;-RUp7{}6X;lYCq6Mr8UbmFxc zufE!LWQ@4NFUrUw5D_jmfC29ihuM?7Z&3Q%I?_OeWU&+5@dk#yFV1mk7_B>O;kUL1 z6uK$k1u7*$uiA`Fy82(uLJ3nD&xdz?KTWJPb2v9;T1V7dZM6OqVD@kxl~BH;(;XI7 z4I$6n+{dQS{8}ON(>#WWx%``o!I8^o(5g3zkzKjU1u-8nYkx@M*>J%RcmXk;AP73j zBj)>HtwZ9I`;}EVG}f_OQ%GX3N{^05P$rVHzUZUX20%!a_`V7H{g<1f; zdr$rz?a#SFKwIpmm-vArhH0c!PEGq%^V8ndaH9s+h^muc`j+Md3a7V#NN6Zd;{+`E zs@AZHHGT2B({g;zJ7FXk7rtf6b(N@(gNIMF81h0Ne+{s0q>Kmw<`8<3!K40drF?e1#ZzP>6)ddmH%h1+U&KEieayF&DHe|qBp zL~Uq3PA8XOK0(P8<9wEJU$9BIzE7zIeT|y2WpjHBXqh@W+g!7{S~RMCd2eKCy|RLx zM^b#1KJO6Iwn6K>yU^UZ`CvEu=XIcoz4?(jhGr!LwzL~35ttKo>_xc|I^wKWd$A>t z2$GT8L~Z3)=q?(xUUXGmYF334_pNf_XJ+P2zVb~MrAv{pUR}(7)80V6dS1;)xxB74VYwcDA;;61`fj*99z9`X3UW}cSZUS*wR{5 z&DGdwB-MQ4u}zetxWWc^8FOFzrP-~j{k-mmt6BF_*7Ax-U?{vI7#||0+4yXi-Jb~u zw7hpu>xh~$C0yPmakMAHVGzSt9^V*WEaC#)*paxfwTKp@?hXfY#~PHIggvmF32D$I zr^;}nbkwK*Dz22vX*GmC$tC^YwbSvIj;WVPubW}Iqv%LXB@zb>Uxn&)d@eHdwo3(Q z8N-X?%vM!3d)(vCNGMgj@KQ88Di;*44>Aps_E|JvKg%&?nI4)yr1$PRj<=hw^$fpY zcz51PF5+sX@F}sBu-T+^d2gOt;_KcqB#wGO4&v9jJ1a_7JM1b>x(I< zD1-^RPwFdBHNLaHNoXK*i`Ps0-Qq$EB0OA)bFFk7G0L{sOcb5tn+8HzALzOB(M1T z(er^nyUpK|D=TKp#wioykjeQ_l!1HsLe85XtHfJhx@LGYXw~h9=!2FE=)fnhv{XjT zpnUFUpGCV$`0NCNO5ua`SIiuRwSPG$C_;yb0li$BJ}5$*ifOY_NStoMM@mBZ!CO{P zcEr1~ls65LkQfV#WI(Do756L2Tn;{m!uCo6Airl&gTAML@WX*F+IXp*)b){O76;(A zNEsLGH%dex^z;GFfsGG3p`4zY8*WjIR)Ln}Y)kKMCZ!8HAvAq+S{Y}#`Met~;CJTI z3z3mKxpg${zGsD5jPoRZ7%ARe=z~vw>dcgPXekJVzO>_PB^f-S^bWka7vph3gRWn&YTNiHlA+>~Gg zNa2|K+INy&^E9B>vfEXir}1$Oh%C_txLQpH5E`MJg_$n&)`;}WmCxXj*0$b@W8-f1v8_1&G=8{3W?l< zaNu!2JwVle_*@n zQZ&BWCE5q#9ByW}ea@98e(sY8Y2dvGXc?YSaW*(Vlc4f>e(!%o=z+;64I3z?2rY?X z;-HeKW!~L-1#U=Yz(*0&?(J4>iM9h`Cn^su` z(J9v&ezfCN_$+Nk#JpOyI!dh#b&P&nvEXKc&A=Xy``2-(dMm!L&TG4?R9d_n4a>oiQ7xgREi1}%WU0i-0ZMs4#O9@niPntB`QaJ zoS$pp8JcNjGGQtv^0&(z)#q5>m>)F@wMs1ymz=k-Kv~DWt0>CmiW`8=XuuixF3LzP z-fK1eROQLAIT8D0G7`IK2iM|XP1^E{Q$U%R1PQ5ZyK%O`Cx$w12-Fx~VF=s=Nh(l7 zQ$c@bmG>otj>^uyGsQBInw7_O*LtNvV5HzajPv<9?FvjCWsqAO+fER)2=}tj0FnPYU|Un}~XOTRHD{ z%YAH$jSMMyHM99$hNcaxU0e{-%j74*C`U2Z9W6!cVBF=d(4T3AzJc`5 zS(Ic+B`V=&e;R+Rr(H=|3>U(KeWMkU$hG zm$$wC&I%~AZn6

m&i)XPrJDRVNPZIM@qQ6?zYotxAah14Cn$l7BGuI_;%x+O?HAv5{;Ou|jMLqx859`rQ$DW{vxlV) zHZ0^!hUi8%H*7QAhsNLYTiY*`&Q7@sYwCb$_|{qZIeeAC+A~DwqE0I7V3mA`>EETY z_7Gw3rY35Sw*D=6W$}5rR9)AI;}X&!*cw_#FV~q@BWtI!r$?lZ~46?w8kvnG&=h zlT)!#RkVHIyv^3~cJ<$dBYB`H&k##HMSM$vkCoB~wKx-*ikGRj1`8#rpi6J?w zLm#wWgZMo}K^iq7^Zb9;%y*Ve~7fNe_4DSL#kNa}kluegXZG=jzoVL!i_DZ6c!BQj%i}-Dz!1 zt8B>9$PXv6^*waQ4sBX=dB4Hb!%V?^Af=DfkZgAdza>jKKBOL6oo(1ZY!3YCamA|? z$8x}+3#MDTdgOktl8j?X6Yt4QLeUj zJ%ue(z0FM>8_QqzxNgL8%O1V3BP)!^?0Q5PmHEj&PKsFgeu|EZ$;`LG;TN28Y@vDbj6pj{TTX zMv)#5sZVR7n)1M>UXX$Dq6ut6HLoQDxWvx2ER*>&{&p0VA!9;V9Q&I(ENj*-Gf6X%$ zyit_9V*9DDk*X{bxt0_MtoE*;TdicGE-0IBaPht7MP`Rbj9P$78To-PhBz)uex0W5 z>KFG4Pi(o-kSxx3&I$3X2N-jovj!5K;4V?ABB>j#ezQulud z7n2vl%!M&ss!l^+3c3`;)q@Kw&`9P*E+M3ck8Qr8ge$oE^YIS`?Rg@${d^5+zcZ+| zOhR{4PG8G37`6mQd*67hXvNXS50H)}aX3J3jc#7n-ER7gGl+T}@hNT@THD*I<* zLs6j{JQJPGmW_(?Izdiqw<<$L6hhqQSJD68gv7Gd-m zAsPY>emT}9^*s}FY(JNJQ@7Saz(T{`#G4(~RRxZhrsU^W`t*vTzM#AwDka5X19m(!I534+d~s=;+Z?$C?^kV{ za(Y#mb*9{dCF*PUBkn|N><{Vr(Cx(y@#I1Ld9+B=Fib2BF!G3r>YjpB*~tuNn7yCa zM3^K6bw;X|nTI-vH7XOetQ_M_#?FkRj6dj|v4tmdD)#1xaI{HU6yElu?El@@r(oCeu=h5hs0riLp_P zEDlyyX*xBvV3(26>Z$l9gUqy|91?t%vT7QQH9(_fTPt#$B{lc1K@)^4 z8A}eIbP0D}@@0$7L3h|A^LJF+`5hUlzD_dLAc+StL_H$_d0$Q_GqhkQ$exUJrWU_p zEz113S3on!8ME=oJF&Yt#aSsNEhS^KrpmHb8B9Dy_Gqa+#+cSSMW4v2Vuf2n0MkD@ zPh;g-X56eCh|7D4PocO~O5v%Gl!1MTy{qke=a0go8%~|!#gEm8($7)rn0N@9!S4I1 zox1wMoxo(+P7O#?Z_e|!d=2eCYt2Zm8hl% zZCsVM$m%vsn>8pS+h`G6pRuLZyTz!qCkOOU)q+TAA}K-=?<%q3h(AZnPy>$&Ba=Df zgfjk7%M)?_eOOtYk5OHB$p1Jo^C8U4CD$KcAg#X0%*}y(z+LB!Z+OC0gLjWzuRf@d z?$S{~JP7Lr>0zOL3&OaBCxIl2rtQ{y>G--I>b_VU#%>;k{n`T`H6kV@4I;@(oVHX< z?E^9V6w_k|$NlQpn_Tpi$`7HMDM5Wn2o9MI1&AyC=YI8=wV1`{!eGRwmm9&;{`Y+| zlo?5}@MOk2?jtUf!JEohWw9V`d_5Twr6WWA3}c6&q>%Nm@xw^!vg)#B>&i?eV3mzh z{R?)0!vnQ&wgijXdua=1#b>WL{v7|1R*;MTAj2A@i+A1c)Q9Zxv`Zx5X)pkMJ!kY9 zK6iY2x(Df{VSVejC7UyQ)5PyWx*+i841Jgq5)#xk`XSxByhj!Fyr=D#7j2r7OD-;XC!D=Me>Ys6a{h6GMktlq>A5E`wec<* zQC(8XlrAI*rW}RVkRRq2Qx*8r&_B=vFnsD1`SsyvQ*$U&c+UWU^kLWiN(MP$D6v3- z#|O~uK;IyNzIULkN)RI^^iJ#L^>3C>ySm28>)Kl5Y&PF5aNW6!E<8Vf6MiIn^7oaM zRMEZXh~DZ&GC}HaLRv)RkI0)6CBl*)xXIrv3}u;|Y;1BpCDGf->BGbI$J5d}Zgsmv zX47GZ|6tz4{GS#8yg^(=aCyze66-T=F`B)=k#p}af)@o`Y|GKZTOJICZT0laDPdjg z&HICZd)%!<^ikE9F$&nvoS}7mZ&vRB9QoXgy~vsVb6>80h$~@jP$4riUhhCDa zex8%rlK7!5lSR@0J(8!sf!-}6@gv4oo6^{Kex*RAtkb%d+_HL8QAr9jB`0^9iKeb+ zhtAjM)2YcxRoB<`o?ysbyY5VZRFlfoi|7MwOu*%^NFs@_Lcd1^)fx6GIN~zdO=^me-q(PgC~^$XCa%y%SUYh~Xmh1uNs+ zP+pQlHbcxFSAV-Fex|%l)@B>96>;M@tLZ~dl}(<-OEO?3*{w_*1CQqdH$jL)P=SfL zBOOw?5Mm)7IMG&f$)aT5of2wN-rkC`BOSDcE=pM&bayYdn%M?kUP|-fGqvR4KyaNEbXLohXlC_^Z(b_S4LIUMeQPj z3eq7B(v5(0N;k-%8|g+`kP?)V?(PH9(p}QsAs`?Phmdag)_K2s@9*mvC_18gSGXd#|Y%gPVnik1_R% zS@Z!y!{jOzA6@X^sB)LM+G-}m%Xm&keUwPn5~n}^szI3t{p2S4y&hMGE8cy|zHV0o z_t*R6FA+8A{0OudMH9(EED^1HJ5#Ms_RtwHUAb1{rPnn)tfP+$XDdG)idz^ys<_K~ zVJUo(#3@BoUp3;N>m}2j!W{%QEMZMl4IvS#{NjGK69XE8kUvF%>H+rr7|hESVvND~ zumnF%?B}LR3TX!Mn2y+4wZRj5=O}K#T=);J>!6efDy?SSh z&WPvUoo(x)nnt(Ibq|(4x{AooNP;y-BW-6?-Mir6gW_-!$>H(#}PJ0T3&5+1i|Jv6?O>SX7cWW_oulL$C&FVd-}J_c6%n zSI1zSc1U4&b3|}o0(bg=54pi+ek%J1MBe33)Vh9nLd2$U&p5Mx(SMKAkMNB;EaORS zb6JhA8rZLaB(#lIz&gf`iyxjlo4%gD9h42RL7o3>3p zpUz1a|1KR*fg6VNo=Gy4Fd9u6Z-Kbx)PRlInnXjezO;j)ye%Wul2ejRA-Cn^jb>y+ znHmj)YUc-+gP3$o*f+^*4s)@u0ka$5%?@)*>aRxibH8f8o(R+KHWK}pjzfiNb%GP4 z79^QsTtQSy+#T17+~e0$@$R|JWWJpF!84J|#xeUC?t}Knhr2a`cH^6~T~PiB0^3j+ zU!>erygpmsTF-YNpwPNnr+DnQF3hMKm~%Oska_F2r=I!wkpC5{ZuejUvs~;~8aXR# z>klAbK?s;A?m&>m8t4sfUg@cj_LXW^27u1t1nAH{g4)c^O}|DpPul76{_?HuEK4Gb zj<~I@?c%M)?j*G3V!>AEq@Nlt+L)f3bF6bSaQ|(tn8p)vx#E|l&lX&pD4x@?)ab&z zvQJS0ViJV{&zSCx&IT zw@$?og?J?)0cHcZF-d5irdArMEvVfts`)scZElSh}RC`I|ID-=NWTFhW54K0r z2-3H-jACha&jcbxA3hZ*rS(3=6zT}2&+Iq+d?1x2;`5`ZsFN@d5@yGv;NtRD@UVe8 znAG`&=;QU6Cor@S3cBx0i?W&aMVbSjm|_a&f4b2RT|pDApFM1BZ6|c|Y73g0QVm`lF;I&vGntABfROfj1GZYHtB5IWjM@xl$0@ zP5t&Moq~)9m!sCpszrz9vdbHV*30eTl&QMMXbFrDv!y!vt`kyd z6e8&$G$m%l0vkJ*A-A&9DKArv1g6iT0i5K_*O@@T^L*CUfC89)bmy8uhDQpIJu%$Q z^Lf8gX7LyzUz=eI?_eAM{d%eM)A$K+Rm(L;E#}DpG4mr@$CYEopx$(~#~lR|xi+J}Zm3UQ3yPQ{=y z&rFPkNAA-@5z*aMe5Is{wCli`IH!(tZ_HcqPHmhr*_wjv1k3lKpQ3mE^ zWK!Xc6A+bEgUjH?w5Y(?1JBG*@jb!K^x7+8Z6hz^7CdTsi%X}tGwlVGlz#C`55Vf{ zp4WB=JnfotDnG&Q8VDD)k?qxu0JT>2m`Iw4&yDL|B?&PxA-^+Cl;{H=+}2G$i{~Mk-0d9or4o|Ds`5O2Mw}7aXwehi^I%;FLFl%ckFRGxt zJTh|Y3Y|2IP#99t=dgOTkE78|FJM&B{eij04Lj?q zXrn|QpH&tpeMm$IeNici7hl64K#yah_R;oViXF#SHl#`XJf}cN#eosU=u3>Wf);K| zk;lvR$MphSU*(m{x;$l%9%*rr$&F&PG9o2~d~5EobPRGu6D(C(n|rG_wEoFl;X6pM zYz-9TZVzbu`}Ye*W)a^}$!IycCA(v3D-;{9I1H6-ou#LW(VhBZa0?Ny7o%9d)11g( zgN2qP??55oxHaiX+09-&jSb&^&#rrM1 z_k32CQ&|}U>g6QRwD{!-SBM2Zb2^{Vs=#-I6xG6312MM%^Jf}N=B{0Fc@By+PjFs{ zR^8W3DVwf!huSqC)Rj8&T{9M@{{pY32`aa$(r@v(!SC6PT-$8d*tPXqeq_7rDP1^x z=X0VU>(Ob}wEpjHlgc)VT%;uAX(Qer2~O;cp+NgbPVCu@9?#Wy$(&Ho51> zr=y+RFX&jbA;{711DcC(Ye;8AvzmRANgfkwZ}j(UZt5CIaW)*(7%Z8vmf4N)Cs3?y z*$n6^tvkcYo)}d8gZXfAFwY+Ju3`Df%6XD--8S;Y`wZ+;wPKg2*JFN}AR&kBAK7o_ik7PVhU&LRp{9}iK@<=Fg4NZX^-$SE zjcZpSQn{}?Z;|o4Kcjo*dPFZa8&mpcU3+ClmGTU|awJ^+fPW$S1xr7>&J5;yAkYn4p-RY%O{tcs`%j zYxh)|lSXHR37ZSGyKhP07@Mtfc4H#nEab_L)QXAoB|6)wcOm@~;5%9nYa3 zYD5rL!}|hK&7KhFY@|YQ^)Nmh8Illr#5t50W7E~FJif8u6YAy}Z$_=~P;Bo}L+C== zYlUO|;*t{no$Nq?ZnYh|dm!3mTl|1P!6N|bj;-O8E>PTn!-$KAr)q>}@h5;#Ccv>8 zTMzrw%o|6iv^RaVv*a?i39Lk~!*Z+Ubedcl-?ZOx0z_ux{&K|;rVL-qc0dU33cR!% z2YWCW5w8PjMca){aC`KerEo7{lN(I>Xt|Y0z}4zEhopf4ndtR^igt~aYV$D(2*xrV z7@ht1w~KcW=C|b3mw@)w7aVe)0Nxl}b{J0P2q)$)wv*wM!+~q;OLeNjUB=X|vm-)B zN9W?@eQVZ_RR^})s=o7=MpuYxh02@lkGJi-9X2X(SCpUGVlahoKX68jr8%|@oNNvi zfnyr}Xx~8}5vZs90Mfce=%jTYVGs%=!VqN=jZwGj^sE|X;<<+ubf=|jDpgp;>!|z{-B(o zi=G@tp-fQ$9&*b#go5=b(YF1z!Ytp^mv35r8u)X3_=Th_d_EmZmJB{47}U6B)oQ9O zhallWJ67T8KX4quj~us$$(EBrX8zs$IC;dva)V}Put#tLie;oK6Kvsdxx5Q}HQYhU zD=M}JnJYFrpHr};k?{H6eFEm4_}WNEH8osN&8)+XKhIhp;N^;f!{b!egZc#zydEAx z9H5cA+7^8ri1NELeE@NKNv%8K2rgFe3xEX?2G0J|(eK+ya#h z;|UoKf!))9o_EQ`o@rmW^bu7CwtGFF?-ORJZL5D(O|>b44scBny5&&XTNJuL91bN#((=+%st)6GI7s6>Upy`&g%%xV?JXwE>AX2?( zxwB$VGOn|x7^KaNs@qRONg!7G6)~DnsnnaE?M-UHYS)xi36zmRsuaHx z2eKJesI}nPcZ75t!wlJF_h~UzEuKw!K5m%dGRl8;uvov*1~-uaf077nzt{Wd^U6A? zikaoKwd9=uX*90-rg&>>EA}??(;5m$7<(v@u)l^wo`DzwA;78x-FN^`d&Kd2pJU=V zJP{3EB7xG2(`9S8$_Nh_Z)}?{Iec!;VsG0(s+RpY%Kj4U=uhG zi~XTE8ft1&7kS3ZEuQc=oPz*be|RDg{QVZtPtjghpd^;oDyH+rfTdP*t8;P#t2m8t z^bQ2Sec^so@KX#vf7D>>m+@|fuZ;E?@Ec{@O7g{5OEEK`3{Vd#w~R~f6H?M`87D07 z&;W{<5Z*4Xpl-=|Mf}(@%E4(>jhT0DbuoLz&x(sleiYkhH{~$9DG3)@?@1{o8BSm9 zs3eI5TI~r&FOc7J7&J@Fd8xZREmt6OGQhuHK)YUGY1|8$-3!rIk}T&j(J0G>O2?FN zbaU&(rn1!4q1xDn?2$}xE2(q4S*CRc60v_2)OV8x?@auQj>LRd95R$&{zfQm}gTgGY3R&_tl(NKwG8)Ps}p(yLS`l zLKXzkS1F=?z619%WNy|N^e4=Q~ zVTetzj{s35iCHvR$bARdp3#^4r&hHFJuY&wH+o#gcc9&b}4$z<(IE?&wJrTHM6 zwFNXMa7*&6M~HPJ3b;-CHKaC*sZKP4yl0J^HB$9!3*IMDmY2{Kl~CFDL;ln=8$3Ic zb}O>fz%MQS;+;pS<=AhDFXtw?{Eid_8Qm$Lkx+?pa-){st*{r%cEyRwN=X^d6Ck^} z3?Z0X5mKt!)Gll&A9}1m22}n1q_vmVJ~>o7%Ilu6P6d+K{C3ZL{(xL4p~t&Z+POc= z^O&n3|B7KC&lAMXh;Xv)D2}A_$5op4C90>`gCgPJA#52B=6QcU_rKeIU}`FHZ#dD^ zVN!pK=P3jC!_}xqTh|Sa&*i*nRBc_M^@2km>}*Qqji()*QfhYu?{b5oZ6knY8uyAz ze$KiRPP^rU_@H4B?5ckKe)k>N^hIG@zkNZ8F9dK6b5MFd+>(NpHp6g;Hc1~csaV5+ z`U9b01U>oR5>b}ZmUx5R$L3c``*_IPvguUrSkyEOs4psA9)^raQ03zYqzu2NPi{oy zWK*LGNQha6FAv9SL-Q+NX&D8U zRnV1H)v4ZQ{m(n%wU#FYUv2i;uVwj(r6DAG z&lSDyjoR$SQbYS^fS!m$FUCP2RK%3;C^GtlQDbIi#t!vCyvQbMf6)+-(~nGzKA=d+ z752-6*a+CNf+x@5cch-oUZS4EU-0|loBK24ajsH1bK*EK}{c%yvmw9j@p(frvWAGy7?6P^t_kEv9uob z0G!))^)GANwiO!3vhC}B7a%6rO;<;+F6{7gW56IdwylW6V*C0C3S#JVU!BsP%Xyad z6G%6&zPjTQI(n){^@7D#zs|GBGXY1o|Y7i2C<&`dvqdo zQy%|3v}-#_g$OG9Vk)Dg4;s>{KSsed_a%G6LaW;Qna%o7uZ(=ndo0mZ=H%6Z{2Ed6 zCyMwB!z{w@czTxdI}CkMk!aoDy0LWb9Iw06v$s>~@B(F}kJLI#6({2sRb2A4sqXE`(f10k0c7Kpxb zEu2%0#5@5^M%lhSysD}_!FWQalG&oV90gziw*j`Po5&CHZbHm6#}oo&2N;PZ%|~r% z5eITwVS+MJw0(3uh z!GW(2=XZi#X3|ispzus;GZrgbpY37sRK9M7hmc&fT*yhj6XuNLvvCxz53cPPG^Ayt0*|vDXc@2=ck2$LA zAhm4e9XIN#i7F__YBKTm@gcnp4>vGDw=@ueP`hyf4?HB<yn-j=88TsC= zQ#i)2JA?g;)A#PG);nkL%c2qr62R&)qEb(RX{63r^0||Q47lGG^KmlJo<60r|785;5CQgKgTfh+T2nEnzaz_eCasC%_q?T z>YhK%YI}(f)}?FcX4YtLdjp}8FN;o=l{FO_B38p!B5@>JlCTlwkyx|4w`YjZ7*P*c z7Wm`ceoTAzCThNp6Z+e?_qE<%Vmm}|CsxJ;jel?WJghaH$$mvcW=oqgjQr< zt8m%(9b~~SF@Cl5J(dCs4`1SE3OanwD<*@2$R`EPf3newK;%&XJRb~#jY6O0?cA*Y ziU-nRS@i0&tu0LY7RL}y3AzmO^m!pTw4Tq#`db^Y4FM$DPwo+V;s459_5iwUIaR3g z2Iiu6I1k4>NzR3j!7fpToSd4L#t)Ee!FyzVw(%xNDOB4mFg$zi5VfwR#%|mv@q6e3 z9_X5=8zVm^2TbrROALIFAPCKNJ>A$0I7w<@J4t9U6QK`Fe^K@csx4+)fu<&&i~mhF z&WtKHoN|D0Eu3!DDmI)h0xguIyBA9a8`(Z2fsQ9g9Ph=fbPLr8Au8R8!ynx#;)nBM zE=rz-lDTjyM?BBGE>r35ln`+qT-nvkG4&r~u@y5l7L;00*?{|w;?n4fFj<@8eyJdD zJZu@FlipLPZf!1iSidD_4e`+ySz1Vd)tH)iNarG2$aBBSQuh~^)G7ooPNTCIsy3N= zWYEwRG}w|BccQpI{Q4R@xe89X2f(#@1merXiYvS~nQ7Y_R=^R-O7&z#AJ%x(c6;@& z3v+H^d1+~>eD6R;^hR~yO)P+Koj?!leswGg_z5?!76`buAi|2|oI9D@R&rZXjhF?OY$8-odMapv<)k=rG&bC%=(76W&dBYn&b7x z+!7BQX=Rj1bv$I+jdAZd3V#Sj?L;U*uzKtgGo{14$aEI(uL5 zUlM#AYgLVN9id0VJhjADl*G28gQ$KXu7fGGLQ3qA(J1Q0DXY z*aQ=b2GQW)WJFti8zO{k{$!%Xeq53glq3y!_KN{G7&ju@Y4$me0(*(8ILAXhq{flr zC&=kmI{DNNuw?}tci7$@p|BSh=->I^y_Mfp7sY~GzG2n^NETOc@B?~}y8gidd<%d_Cb zxZTuRP_F{z*{Xq_T?v4mFV^Wkha$}T4en04}FM(c;Xt3<^)&i_}F z9Px@u`~=n6HoO$4jM0j6wzFMMz2D?CgMt2`4dSiEthpM0rGTK!F}X#Q5F&+W?3}9^ zwT{Q&l)qq%r(GE$FVrrFL@U)6mo51%Cf6e!k~@JIT1|+jEEeEct!w{LH%AHKV{fzt z;y<;~;2nAKVqtp4?&r@wlbU%qaHMh}<1Wn%v^Tr|UoAk{)6JH?dfK9N4O{ib>~Uvu z5fa^%`$Lc4An$arxD4KzR`!t(!QY-!cz2LEwsa1?X&zT^-3nS`>U~T2IWkumhbYYWH4IA1j8&?? zLOO{e=S#kfVGKt{oZ!`5@)l2^+&XBD%>}!eUy_GOLhbizy9ptKx$~?exUR!cYI~B-l(DBvaVM&pLZ5XKCcUlS-4{5(s>> z-pOt>Zp9KjKo<>6fztPCDKpK!P-nHI_y_QFyQ)56(1|Rbt9^)wnE;5@^%igY08mrF zA+fg>!>e0qj$3dT-bX)3VFnO_zaiy)X|}?feB>}rhI^3YtTmYjXORFVQYoU!@J;+b z-VWww$D?JBsuv-zQ9Su!FWL!;Iq4>47@=+VVgb1#u}f?(%z&)opL~F$8Wk}!57NGP zDU0Wq`A_5!!%+Iqwv?J8F033<&z%US-JYcDC=@XD=@cIPI+RGgfAZ>Y`{iP!9(nS> zpSLIbt@hnSMvDnw4Bq;ZhO8xAYJS{3GA!^5{5xEMW^SCsRK@44e0-JOvvk%iq)RK2 zi>?dhL${(JWmyXm|5uLv%V^DDB%@yAeOPw*DFS9C7JvGeX&OHJ%0bVz*dOdc)h`yJ zPxY1I&_6`e3ny!affVOG;kL1sCByxD676Ur=x#I|~W5d+Q| zwvp4bw~jxr?uE{YhW=(z$olwVQ+qg?-EN^dDGX2c4D~m~=Lp02oh}71mW#en8Wd~S zS@AoL`Xn(W{d;uZ`IRH+hFm5W5p9Dz?UhP%l7BjHg%d}~gYAUp329Q@h=?9zK-H13 z)tpLI-4MTIbgc2q)Wv2#+Bopkp*;CIZVs8`u{djaqhv>ESr(;$GPa^CL|FnsFrU#+ z`#$9YV)_1ob}R+ZiuHB{2A_BT!D%Mb-tZ3qr-TjX?Un9XDBzm%&iNE$-1#?v?n&PL z2dIpK-G#q{?2`&rWuHEJbzxm_;Si?i=)GU^_|w6J-pgn7G{w)-P|6UtW1sx}=dgmT z{yxxi?np?y{{>)eN5hHUGT>|Lv5m9>>S7;knDHm7dm%$Eji%L&dvOJkKy0)s8gxc> z=Qm3@!f#n=Ew4Z+Q;Gx???aLapmu;^Ky&mq2L2+e@{y@x9Xm1VGBC&#`@D8!GDNqy z?=pE>BxK2Y1So{b1pfTuu=e)JfbZ4m-X{*EXPYY2Jmwdr*LSdyuYN6U@y+Bn$gECU z7mF?k5lT8-k)DoiBXcq0Gm_Sze4AM2syhe~LVuUmuqy5Q<|$~u4XCSqB)2d8 zM4N?N<3f|b!jHh#w3AzPm*7U*UL7j#KX*q(#$kd4#(XeTQcldeFopk~zDvHz!;lam=5Nzgnx#TyIO0uhGC~NJ}F=BVFQI6kEtTK=6kZ7 z+@<@CWLY=*yyZ=INU6otiRHtmY0tChtGiy?cq~PVJVh66y~Fb2nf)m=db&E~h+EPh zH5yMfI~cVUKj!gW3b!Og(i*aUx{6Adi)B6_gB=7?HU`);ROoeYxdtl?A zAF6rtfUqQDH%90RLO1>O6-6;iq_tEgKIG$8ie)10*Q67F#4JcVt&K~0A>h<#>piCD zb{1?a%z+>!9#Wm*HmhT6{|G`*-a{b1fW@q&EN)ZLt(&87GyR~r`C1)eYO9PdRANp> z=&v5BCW}|f<sx1GewcfCn`f0>`|%oe8n37%%ZxC`7_ zDG5y|Ls@rhirY?mmUqKDSw>WTbFn>d`rZSTZPO!4*_3XirWj2VE%c;g^wFRO={-$u zNbPZFQ>SdTalBTWwNkOtPl567!R;zY- zVJ-O1@Ag?j@gd9`ykZ-D^kr~aU{ZwNj$OdjR&mN(OJ|VrUJ2k^7qBNLpf{G4mu~^S zi=nY19m%)D|B2oPzx^nj5ONGONXq)O78<)KdPvm06LReKzd#r z=v`~>``5=T$gU+ii)bViBhc}XY31OnuBQJ&7JZE!f&Go}!}7`XM(sHVy)w2SWnI`b zap@Gz6<=J#CyvyBlioNfjSSohVP@%~{87g-J%!wN=IE*kImOa3MZEjd6vV|`>vSVq zUtNyOXO}R4l#HER`#hUyW!xH^67Blg7O|nZdELJF`0LM5bBt|i9#fk0b&TNYb;gZJ z<=x>MzirGT^|(}~bY;w+LW&534-Y~QVFGUsZ0_mu_dU2)MfISPW-+f(Lp7( zIrAm+)v@C!-5+i^9yq+m%YX6X%l=8-N8&gW>ubxE2MDn{GS^U(5$OTW>*0zYKi|RWbmj5w&!9c4MPw*#U*`6(xLX zNbu2iL7D>-(d?a5{XclTydqt-*y=tQPGc*V>s6%KRMxu2SD;>Bn?9#wX`^s_%T$a1 zw7?^2tg8vnl3N?=*p8Hym^yWipsmw+M!eHAn=oHx*u;-b;9x z?C^|)D1CC@f~jK?_Sa*~r>wi%tTi|D9`{hTPVl{AF;wpA_(J-aV$WwMOHCKy#Itja z{ZnKS{kbL{Kv15kja;gs`e`V&o;)@QS9i31U|B-s7P756S6ul)GT0OX~;O z>8i}xd6eSgZ2uhfNWfvnEA6Gjc5J<#)$|en16m*R_Gk)#X)599{VdueS-FxP*ooZQ zvcTZqh_0;@1tKue>SRIgG9w!_J@WrU(SbgqmhBPnzIFXx2>*fKG+U#oaU!6Yg>!K6 zoTB$Ya6WzU2yIXZ;BJd$cK*p{&@x%fAQv}4B`v5(%y zqvnss2v9ta4N}IHrY1^&Fo(;MqtM(w7htQz! zXS81Jm$S#a9~PYk2|WIWkDYCgQ=;$B(kk@YsN3vL)keN?6!q_V?y>&!pc#^ehjqNy z-d&Ji&KIOE;?1|wMk|Z(eu4SwU*eO>opCW#C$`SvxA-z)d8~1mly1E;gR)nGWx0(_ zZv@H;qEY#68MURB#X9+Mn4`5=KEX1|F3jMPN6fa#kbShz z0(opF(4fnwGKpUs|91XcW^^-|#4~s*bli;#*zO(OBxllGrB-r=tnWO4l2Tro1FE$5 z&Yxp=c}%)PV%L|6-ddDS;iqG4v~Y8A{eu(NBpnl(>MZfC6fXO&B1=3N)+E}y;CLPxL>_}k7f0vD?<`EZp z+4y43PLe88(r$;P5evGuE}DKS=186_+pqL8j1!JH);6aw&X6PAwY}FAUVa*%SftIO{t`@g{)0tMR3&^jU z#4hz1y5yL7=R@=Y|ALaw7eNa9P(1!l<_~WKkNMQ@K?zAHsnEV@9ZMT1p9R7Vi$qvL=1CWf7 zZR>Y}HxH7?yKO)M=Gtpnmglx$U1-lCCJC)O`S+(@^i!PSpL&^pY%{u4;b9@JL8U{n zaj!DH*PSj%8Yo5z=*xP?Q{}9=G19p4;?XN91it5VFnF zV;$!5-e9qVdP?gAO!G>Zu8p=X&B=LuhkEUm2t~$dUwuPvmLn8>yz}ixajH0J-jZwj zq;^7dL8RT7O`YJ?+xg5|V;60*zwsA3&g3Z9>b%E%FSfZKmA&gS7{=PGOajRQj&qs@ z*YXhtztspI*rRmEhDq3c%{SJfIolKP*JmL-C?g5K;*HXH<%EGV6vn%>QZ)7uGV5#4|@oV?j$qT0mr}v;PErv#b9)j-Usk7 zE{jJx`oQ$?MVSDY$O?H&AflwKi&gj}nBnQ5aV`^;fs}OdPwPQb$A^fyGgyr}fp&@< zqdJLhE$ytxS6+=ag>}Tgf^Id~tzr*>wItpe60PBh5oss?M5o=PWt$G@H)|Kx&L!8oqnrK>+l4tB zcV`RrE?Xyi#a1-y~u*!Ls)jvgT%@JvaRrxGuwR zWamRLndU2&5pV+FD)W7atJnnG_M6?UKNUuzz9(^6rE@-+fQUvT<%@=t&mMehUIs$~ zXcMHKKF8PqLlsG2Lk0ps)va)t20T}|^vX-6u-%Eg=V}Ftj6~(>Lz@j-skXDXwRb=b zVc^_qbh|JoANf)KxL#n&JXewYgABEk{0utp(tDgG{%wKMja!c*xGaN3udXzo_xDX? zi{C|cRweN7Xkf`Av)iY5`COxIm3=}+3qe#xop=V#LrAt6fCLY9sZ~>KCP+Qf@Gv_u zT@2XH%J^ynBf%3E{!U%%?}m(!_6)uX2y^5<0zMJv3{i2K&5wgA+4YW0$v@4j;nJMg zZU)3ZU=!a`5C8nwot3n|l(zBWfh^X))bL6Du{-h3G|Qs+mOJiXMX^iy-fF2)l&xeu zG>W!UdmN`~Ex&Aiv*t=U?kWBQ?ypBdG5&Av0pdAQ?@cDwn)RrNq4@GF*oh3a^B%g| z4@=Ho(-^%eHlndLScQ?|YYh3~l=y3sF8g3O<8{sup+t0cqK3Fd=%aQV`FrRnZU zyXe6+f)7xc=u{|o-Kg`6UxThlb(W(^pUkNn`wqm4vY;S=6x%=U)&J-eli$xK8UvO1 zHsI0r)phTIer5zqge!Un1sX-q#wE=0gUE^o;^>pk92dZ= z%y|jN(tH3?ci6o)yzlx1Bu?@HUliCH%>ZN39f~`I^Vf1Xd4jFs-s6x{HK@-OupjPw zZ4OPlba2W^-XnBS*5UiCs8Mt9lV)^jS`Dj8La1C%G{?+zK8i99CR@76!(w$x`zm+S z`Ep`G`TdXb!^nZz?C{PamR#>0`JcbekYL8F2pG5xLbLo|kxE$PxpY{yXLD z`>NJ^`X%6b{^Se;zBfbGZ=g!K7Ny%l0QqSDn*g!qpy zgC+P^kZ}tocFHAmzK8UPnjy|cA#r2RilrM#J#=ld7&L^_I=;y<(~Vtcl@qZbKBfGo;*wc7em6*A4{*r=lej*1z|Cd~RJx%r%ADaG{yoru=B z`hp$XN7w2NyF$fN(CFfxn7-~V`qYzgN#%_5V8n=-R@$RWGR%S!CSXmnNNH@3)9Zm< zM40}hgwzUGc5!fp=TopL+Ri^h-S@hA@vG`#xG))8`Hu|abbM%h>3SoNuO?m-7b2Zjl{;e%H>-1p~Wg%aC*Zk<7wa61)PdxBFTj#JJg>;B*t{tRi>lt?&(2gKrbIA?{pCE|RsvVb zvB))48;!#`-hnqe+%WKTo0&4OT>iGCP!`ywk-tVq2YnNxTER!4Duz2mmi-=mfJxhWdhS&gjrcx;hXQ0RV6R*?6bUS`G*JqRgd2=7NfQn zi9fZG(?A%ZsHUd&Tb2Sl$8n_%wuy9@4&r?n!^wqjaD9BUKv@YMXtsSIXfroe4niY> zTrA~DzCQte)^=5UQYPTg%P1FZzfzDsbVfx#6El@ld{&7P!yt(!Z;L7yB%VrL$Dv8v zhPz>xmgJ%8^{BcBMZ(W?+?>x=ba}##-x^I?Quf?VwV(oD`MMl4Y-Ub1;39$!Gg-2w zJx=yT-^_>RBP@QXxK`2p2;|AnZwG^Z4WXLZ$gZm{jKK_|b!w_eF=FP_t~^v~dP+)j z0-Q=3vI$hR$TSh}tivJN#~)hd8BwMC^Rpp8bQ0JfAKH(Tn(6Fk=qG>x(sL *OV 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 literal 0 HcmV?d00001 diff --git a/assets/badges/github.png b/assets/badges/github.png new file mode 100644 index 0000000000000000000000000000000000000000..5129ff38b7182d0daccacea2a148742cb9d4a68f GIT binary patch literal 6930 zcma)BhdZ22v=>BgQIn1GN%XQo^dM1VS#2#L2usw}MG09#lrL(s+A1LuyJGdQdek69 ziMA_hgb-zsV0G^M-RJ%R_j%qjbLO0xGc)hJ&+q)sOLNovtV|##Dk>^gh@l>gii#Qv zlsg#cfG6dp1_XF9`WxB?QBg5-{dZrY%FVwD2rmV}?(0xh4)d=86n0IbTrpg${omZRkQvu(xYaQ!&(BRcMFfKWe_fuwca?5O zoE`5k+>JVawlULwPBcV|uV9c4LHAyn1{O=(4mmM_R*`6}k{J z)_xs=E`IirzQj9v?f38Bjww8%5VT^UN; zAJ!bO)LZEXdFW%riwTJnjTzfr9r@T0ZhJg@{IA9KwdR+9 z9>1>VHs;sAjPuhCn5$9(CTzU+^#&@(HMu|fWlYbS98Qc_%GrMgpMT46IUqhp{a3b4Eaw!!04rF(<(J~t=R2UP zs_Ic`-xPR0GWRC4MJM5I3KAhhTrUuT?8?cx8mQj5I@cKRzA5r7eD~|Ec16iEVE>0s z4z@@5R&E$xd-vCq-a&UmIWCAlEK>9`9qzNvz2?yEM}Pe%5*9ki2MbXb7bFG?vqCMU zd-^}^1qB6_Rs7~#ckBaO7urw}ZFeYbOGBu(@O^wv;w~ub_+oF(GJSfo>_ML7bd>Rl zUoxr{b@+UzPbf;UR3m)vcUKVq@OF0sdtTA8VCYHX=I+m)q+NEKt0yg@zYtDFPFt_) z*rU2@-A9Hmo(}fdI|gV8X@u?QGJIm_J2^i)S#8L^1j`ZP6Gcy{WfA3<73 zKi(FR!}_ldzqx2P1#=GHD)NH1&u9JMmx5XTC9Sa8C2>nm-ME zzdWbo`Zl75Nw;thJ$m23zi7Ms&fq{$^bFSc>5ApDy+l@@4YQQ$9LeX<|4OR-YNNwb zCLz7U&$ZBahrw7#>)GKVQdi0VbDZB8LY`h8 zSZFgRqBP{&ky4QhZSA!VYr=I<0bYLm^{m4;4Sow-n#!#P&BE&WtCCehkwnrPxL7vN z)qhn?z=`he?)vdqupmuZT+;o9QG)ec0T(shCk0&{IzH`~OWN=aeN|+FD7E}0^1GnH zsHKQ$9@R&8T^^~B3xA>XU{6_~5j!};4?oh6d1GLFeC&eP(`wx8KTqo-L$~MOz*B@( zuhi9IH zTTDjqBJYLeaC*0B=R$;Z2{a%f^Ku1|#T1$==^7BnXFe+`~OGBt())inWWl zchuLCOSN`ey)MeF#7br8aDF;grSXdmMXJ0?@VqYSXhTA88WDTK@8`)Z*)LIoo28)BwS!U#H?=d^UOOF!ZP9=h1dMJ!cN zW09$FBo2XZtw@DkaCE8P33Ce9ac*Ug5lZ$n_zNu>$v z54OH#l>_q)4tS1^hjTv-O?sWL6>EQ7oJ1yFU*PX96q3kx*ZV_@*_71#2&CCHQgaXd zWi#vYoY+p$bi_c4Ev@C40-h1ts;$li)=c$W(=;JtX{U`@Jnl44=3#7uB(Hy&`2;1$ zDT!8?@kI9{8e3KCMH6t8W_1J+U0>n=NoEuW&iv-55y=ki2AX?J-A7%n#_g3c{h)X(jx$(gR#xrQvms=JV}vBX7_0AV`5^mv zD=8<4G6vbG{oXNw3L4i?T@m%2HeT|LFe$En()F!?vn!LYX&-sO67BzS`)Htvyv1-& zU*VSm)tprNw!_%5cc6e6II!}^C;n8;lnHEww62HqNNb1?pIKYFc~F{7&#i2xhwcMhnPqXF6crD`bEDu(>Ld0X_EV`@-`b2Y`zXoJwyk>Pbj z1O(P#4nYbL2d&6eX6}s=T<U;o&A9wsspEng<Fcnc6v24 zTA2$2t87pVNvk8!m(+CY+58PNLT5bmcA6f1Wk1kE*Y{|a@y0BJsY`fNSNT=YQ!_KO zTc?jGs?4?3y|lN(Yj7-FSQgd%>(?_yA)+&SPDX0dV7Iat9Qn;d#k1l?A2h#8DZi>q zVE=j^2IZDD*_Rfio|Yn-)@?=Ni%fB zYVk@+N25iCN`H4KKAB;v(EXqQLBn(3(WLVv{S6!bPhDBgqV!A6))l+Azsvahx}<;+ z^m^!U_L)q?J7W);Qsb<|ATZBF7AUlEgn$pR;fEciUS3TNl7PS@0jYOnUecYosO(*v zV*4Bibnz@815S;1;Kx3(3wc9=LW>9_NCGVP%ZoxhWx=6~VcdL}DHU09ic^r%deuyY zzf9l&3M>jS!Zvnf1W6)E?g%LRV`Pnn{SOlV|? z{V>yjwSV%~LidH2*X zbE*VHVNSMbx%p6mW&{x$Or9nfe3cZF69Dj@+H!$f%hyJX&!3-E1drSOP` z3Eu4V802_-5D|JN)0rsOb0n`<(OqIzn0pm~Aw$Qx|J1buc;jWj{`#Z=9wHwtNiwLm zcT$8_x)6F;(-?2zbq=DGW+=$vU>r5bPjw zXB@}w==E%nXBza)0)N=_DGsSWmh{BmAR-2obW>MsD|AbxBQ42?JBzwkW?DI*IcvYy z*U^fPCUpZrBd?CEJaU7>VCZ#oy*Nn{ni@JpRBcMNtqjViLA=DH5fJpc#RO4eY^;kT zU!)XFC+7L~ewv`Z62CEez}0C?qTJNsW*4lo0wDs~c%U~PTAB2ug6~4<;ck=D)#2(r zfr!o~q5R@$?kgDY;rTW~!O~Q4RJ;?SU-+au?ibbc$+yhv{Adv$?a8l9{+*+$1cBPA zv-%KW5zWZcu!$RqixD$-b+O{!D|blNGHCdFd<3SxF~QbXN#OQz7L1-FW{iL&4ZkqO z^OQ6R)t@@uo-ob^D*<3abD_%sosnv?0K#T5(OKnXpp0oT6WX5H90i9cT15z~^#(N; zN;NjY6KO-A43o-+XHk9R7D1a=3>N!;y5)CI{O$S~fb+tM$yktWLD#ng1q4JHV_N&?Tc6hEIDc7b zZ+*QAI!+-&?7Ev~slij0nyBNw*2L!Jh3e1rZ&6_j>KYpS@M|SD987f2?rvLR{RTA? zZctkei;J1b(TvOSm)R4au)(adiJVz6<^hTNOBWOCo39`=u~_@b1^(S?;#JpK>bS279nQF3ndOp+Z~k0j4jG8Ub7p?bILn&lJ6JQHbL8Q|1frUQ z1o9RG1S0BwP+CcasWBbOS_b!2mM-48oUoKZ%_r`@tFxJJ$DM+oP}(^K@aNRdR3ZkB ztH}%JDhL~@h2Bdxcf|*IR5o_#S^8|{P-_U=fZBS$>0#rl75lV|kzTK~fZ)p+yV;lf zzIoMa8Pw55d*+30Cs%A0V^|-#RQUx&GXnoV zpOw!$U#$7aKOrM7c@g_lO(R9XdBz_MPf2l=rIK)8I_f@8Ns4}{t*u=deSd$*ddKF2 zl&r<0xu1szzrJQ)hYY;zC38DxKPmllR{5nPwq^}O_n;Tm-OOz{uEe@3teRUx(l=2CU!;cLkI6TMi)FZmM+HP#Ch&fUyM>V1D||ROyMu$d7ci(d#21XzXV}rwL-{y2OJs@xF~+l`H;#tyx*2V`0`YO8TR^ft?vv#!Ye|eD7J3R0C^_ zloVCmtz3tA^0}qIuJE>i8Cm4efsmS2S0lm@cwWzm+63(q5U}AJ!P(Z!@u0k^;Q|vh zgOSJO=ZO}F{~=Xprix6u-Hv{VcqNI5p2z;Pxy(<5|15NAMV-{=%y72v-jJkd<{INW zzDJru9u^X;yt007(Jpz5$svra{w-?S8f*%&l5k7GiQ=AD`b>K!8qMXyXiW<&t#gVs z07_!)vr&MM@!R_f*eSx2m)Bu0j+#r{Z(xVLcBQ#X3;B$*NbT8Xh@i2RHBk{a*d#%| z9Q@?TM6pvQpkG& zJZz(Uyd3n<_@1PvM5XLxwf#adHRO+T!fVW|rI)$kQE1;Vx4lcdw=V#$|0z$_%t7&7 z(t%;-htiW!t5^U2Jiq18yw{yl_2_Q7aU!za_F9qt5;w)$@QI}iSkq^Wl%cJCYu z-^xl#-1*exza_g8z{4vp_Qv%ASL(j?BR$D7sMD`7{Od}&)^eEpeIJueLASd5$@*B8Nj5g)C3Pj9ZS9)A zqTX{+Z*@6gML%}^bwRG&J)XYC43p;xMw)eBf|7EwSr(5^AJXwLrJenCP6+z&F}ZY_ zGi#d6jZMmdb8^+kWbhoKy|v_`nZ8YZ`y4lf$Up#Lqc=){IN>TqSsW@@<>L`AaB+#q zZ~UiPS<@QNd?w!1yA9#_bU_xQEl zfj7QQ`@WIHC!!LcYg5=4Ki)p=Wy2Ka^0r^0PcWc0AZ{!yOt)lWj>PeG@E;QNNGe2? zevzQby?x29>@8SQX5q?7{z`Cv%0Ut3(JPiuV-v0WBy%_EB%70U<-FWXu&yJ8RuAvF5E#4|yE{Jj4oXKZh!XS3yD7wq1liPf+*KR(iq-?@~P=XI{G zscF-E$p`SH#u!YW-I5QxpZhhjMm0n!4(l`!^I_m-eH!GeF#+GU(%FPZ*)$Jy(3(C= zji0Yr!c3mN%lTOKcI>@+(O+4m8T`n_?&Cy_Me*kBSq?|?zt81c_I}^& z78hs>7iX#bW&COWJBMfk*N!&gXj&-$+IX5V$DkvsyOIQ8Yu z?Dha=?b0EpKg!E&CU%>j2Ims&BHF!#Xe`sreei$Mk&TVM<>9olo@JfzcSX)T_dFQSBLX|>Up||D89LmHBPfRKkgq4pp+ZB|cB9OYqzxZ;Y zbNgo~=XBT8a>>2FHjJhkk*PFQ4)dpK%8R&QW}faTb^$w3_`LX}JTq6sX?5$2*^uG= z`}Z$=E+S;lWH&XHLTZfX8?*gGLX_7}1WF3%Bh-NKCg0DJ!}^6#ZHIl>ZSN}Okpla+ zCeO04A$~!@t+^GE&8`lL1fgB5OP;yK)c(nASC#^SzjkZ>g=6aY2bRZ9AWx|O!w4q@ ziHh#Fq1&u_W1(}Tq^h(t4L3(1z+A64c&Xp@yUVSJ* ztb=f>i5AHm?#TEy9rIEf;FlHo(di

Fy4IIj&2H2AE_|b|8p`j0w_-0nM5$P=KwD z(4)tpX4HX1fvn{J^3U^%fb1lX`9OMapMR=Jw|a;U=NpbN(z~OymSWitjs{F+t~G z#FXnQ|J%iyTZUwJ0EQdWmiaN#&?9tf))R1*_)a{pHAjIzp}%(d7!RcYQG2n{Kk3DI zFe1=ty-YzfY^UQJpvSAvE1doWS_u~R62NtLcSlj4RlEi$c_T;OwXZTRdbjM4u&w)P zl=j=f+W;!E1_*+&@-KpqUu`K+&y_3mn3+)j_Zb}jjcN~WKR^7lw6fHf1|(Zxrg!2O zcf{(OV@0AFTXe?%{0Z1SJ=z7b9g_5J-DI1e5)d1mj~%fNMU1p1KpbQn04&((2Z Date: Tue, 3 Jun 2025 23:40:17 -0700 Subject: [PATCH 96/99] Added Google Play Store badge --- assets/badges/googleplaystore.png | Bin 0 -> 4904 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 assets/badges/googleplaystore.png diff --git a/assets/badges/googleplaystore.png b/assets/badges/googleplaystore.png new file mode 100644 index 0000000000000000000000000000000000000000..131f3acaa252a863c3b694d0f522ea750aebd81c GIT binary patch literal 4904 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 literal 0 HcmV?d00001 From 5346776d8fbb89f997c3eccefc4e154cbd049b81 Mon Sep 17 00:00:00 2001 From: Nathan Grennan Date: Tue, 3 Jun 2025 23:47:08 -0700 Subject: [PATCH 97/99] Updated README.md --- README.md | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 0b34ce973..ffc24ff43 100644 --- a/README.md +++ b/README.md @@ -37,17 +37,18 @@ is [Discoverium](https://github.com/cygnusx-1-org/Discoverium/). It has a search function, and lets you add apps to the list for keeping the apps updated.

- Get it on Google Play + + + Get it on the Google Play Store + - - - Get it on Discoverium - - -     - + + + + Get it on Discoverium + + + Get it on Github From f3203b8b14b64705cc3144b1d45b6ddd4ad5f6a1 Mon Sep 17 00:00:00 2001 From: Nathan Grennan Date: Tue, 3 Jun 2025 23:48:08 -0700 Subject: [PATCH 98/99] Updated README.md --- README.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/README.md b/README.md index ffc24ff43..86f95a965 100644 --- a/README.md +++ b/README.md @@ -24,10 +24,6 @@ Slide is an open-source, ad-free [Reddit](https://www.reddit.com) browser for Android. It is based around the [Java Reddit API Wrapper](https://github.com/mattbdean/JRAW). -Slide is available here on [GitHub](https://github.com/) in -[releases](https://github.com/edgan/Slide/releases), and -[Google Play Store](https://play.google.com/store/apps/details?id=me.edgan.redditslide&hl=en_US). -


# Installation From 8b459ba3d6869fdd09f781452cb2169b1172122b Mon Sep 17 00:00:00 2001 From: Nathan Grennan Date: Wed, 4 Jun 2025 01:06:53 -0700 Subject: [PATCH 99/99] Added github-banner.png --- assets/banners/github-banner.png | Bin 0 -> 794677 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 assets/banners/github-banner.png diff --git a/assets/banners/github-banner.png b/assets/banners/github-banner.png new file mode 100644 index 0000000000000000000000000000000000000000..021fa4d2d0c6d9b3aeac5d7c2710956dd6b69cef GIT binary patch literal 794677 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

1kM%VBHV?m$y!y*qMZ%i5f3eQj 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

d(*oVwlFbeWNz$Mk))(n z098P$zXwS*)B>o9`OZ8i%SKGyD(lajao;(Fs0kpDKRmac8YmrAYSWL+xl zi&n9ngTb=q5VBnXs5vgNRLgL&9A&2>7yN!R&;{;fapAN}D!^KaPa-@Cbs ztC=fDpi2f4_vpZ_)``920Ui;=&aK-h85q1WTAa?K3<9k*+lTd{m;P>a!^Z;&1EhL# zqf%pX5UDx;(&Gh>J)rwT3+#sl)yGm*&f}>*CYK}qr~d|TcWn=gFdkAe{Z|Ez@G8|t z2CxsCq{>kCcx_==Z&xO=S5zQ+c3fQ2oPih3xIZuH6lNVC@R?qEO0o~T=!nN^Y?fDq z>RkPz*!eg~xyOj03M9En{I%n_|rcvef*vcA zb05x1REcCo?XNnFsjyI9WJf<>-Z@AM;hdhSBh)9Zl_E8ZJv4}T7^9g|AYu^Xnp`xZ zOq}XvL{(Q}+A+V*%gY$Iv-t4VgF7AxvFRwx4_jOz8oWUnE`C?ehT>c{Mjz{N>SbcE z0e)UL!%*Q5u1FER_`&MbX^i)xg&BiSEx^3t#C?yu`{XJ`v26^{qczI=ss!_Ob6YMZ zX|jdL=4ZbBq*Z4vdd9g8@olYSY>w^5`dTaEsA2Gi2U7Y$FHm!lYZXVF zn=(9=`CD~~<;z&Bo>3l=_E=n25eTn?G`S@k1c&Hyv((uYZhGeFy0kAcHySDWt2txr zTzvOqr3@EC%RmgXWp>{B^2c8g<;Uhq*?7b)p?u2KFiE*035&3I%_+r>AA#UK>~rH| zFV$(xABr~4GR4A+aOeW{$~Nu-YSR~D0?4j-UKwFX6Oa;jzUQ=T-xEn+nw+(I2QZiG zJ_fO%p~4vtoBDWXL+Hm;IQ129ahT&45d75^Af{TwOqS4JEuxn!hsmL~s()CEi}Y4& z9dmVezu%!MOU3xfWY^8m3`YfOWCIR#aC{62GBr!ud5`QP;O3jNmfZ8vmuaC=UK#q< zkU%Fv*#Y;JyT4{jj#!6Ml5@LN_J#_S+yyG9S|hHn%?ui4&slZgYRO09`($ium1luA zVb9Bpl4`tKmk~VRx$KEwuMh&p!-afxPyWcO>H%qRxZfnLyL)~?2S|bFGl^81(QQ5ZL z6$>zjdCJpL7h(~!5vb>gXmnq(WQ<8zZow>suek(_oNA}VZc(G>MmN49DVp5ByJwn_ zsFgoZF(1uhFuOx_(ayk~XH)`!TMLZbBep=G=Du?LQ4hWBg^N@pfm#n=FtR;yQ7H*V zrV(+-krDy8Z+DoH=1MKKxMg6F2bxt;Ha(Y>WDd`S@?6{Gi}w5Wv=>+Zm!vOGA$YLA zpjgq*M!UOKey0FDkMw*1>rzU)H<$Y)Q&x67r}qiA!HOeaRbxoD!c7q~kYN>p$xgwk z^1jg+qp3sP++3EcFIft4{q*va&;H?G{Fnc%{eAzK-S;cMn^+i`JVhy!c%a>Okt4y* zH^&MMh=#NW52uk(ep5Ya$Jt^H_C`SzSO6Z%fpGTZh4&k^x`+EKA z_4=cK>~H<$UwiZX+pn(Wdcmx8h@KN5<(#4FG?tYnVkU+(GOs}?4_Lygl=nn&q&GDb z)H)KZ=;K9ze;B<9S)FA8z@rHz41GC9Pz zdx-8BfRf>+IbF`=VmJ7r2fj*T_wXs%U0g-43ChY*A$lX705G={N5-ftTHHF6O1aq4 zVm^{Po0QKa6iWH3i;k0RU7h5ESg}FGWm`Dp1=|4_rdG06Nz4IG`mTvAfkHMEqL(nn zVCsbmIu!$%Oy3C0byCHGmyq&$B0W^3^LV{7D{`i2IRZ9fX@^V3{XT;8qNdZrNV;BG zoK%-?)@FLh;5jXEOSN|G_pRbGm6DAStyVm}_{0*z65fxUw`eGz*Luk5Dl~9vpJYUe z8%;7R)vetJ<}{!N3N~ zQfD;uy83{wr29g?nWe}FnR+O0^6C=3+;zL|XMB41^5Lf+-~IIChqu0;Y`dylvR`J&STUjMZj2U`P1yj%*19U&IFWru(o#G?S8yC1s#pq3`e+gL^5vpbH@S7x zV6F zD2LVMNmGwxTv@s@6sW;GR8=>QH~0C>f-y^#jW1n8<(|d!S``9O&d*#7n{3|aLF2>l zjD-<;mz<6YH6NLUS^F|d8I>qU@;?{Ve!5C6ZW&Ek@(R01w(v_?-6saoEBOd3c{$Ru z;t)gUBttdy0lp;fdKEWwESSeCIAs3YzxtWq`PcsZFaN23cE7y%T1!k9g0?2XcyJgO z%{}?c@i&S^k5+t1rhlXGtDWgxo`g=(9I{dz3TJftSBVcbYH!B0%T&J=n6I|9J`zLZ zq0jc3h;=YxGlwf)pm&Jp=DA*5MzW!~#qM3$aaWMrW^LO(8RrZ!tm8&#J$alr;ZQe~UiRB=%qx7N0^4wG-m z+<2gSYe9vH*lF|V^^Fhe59a0IdFkoF!|)-Y>K3`JN1BotuZ&k|X4p)+F4H%|TU$Vz zeBQkK4V_T^Mn8PIzrlBY_~v^*|MA!U$IotWUe+6ROG!^(Y>d@FgRQy7q}D9ukykpI zGMHps&RCv!Bo!6eTe|Y*SZn)G*r91MQCgVt5@qaMjMOz;o;=MSoZ3`*29aa|DRO8aBppMmmLJq0pS9tAau}#B!mf)RUc=J>$;z zYc%@y3=Se28Rl$xH5$iGx_lcLP*Jl#Q-iF-FT) zwbqwE{th!UsH(%DDwvluGkB&sOp@Gaqq&M4;<_|r@!@C&4eaNvU6_VOl5@$4bx0kgHS}8RySjc+3lvsd zDhk=G6r#b_1}4k)9V$L^)3KXq)LqeNkm+L<4GGV~WGTmBv-eD<@E`X~DFqaT9mcgeCTI zP-3ZqbWmcCn6afyE2rWDQO0;rxfGjs!wW8=|C$7l_*pvbMx`P0~}=7~Y>FD_t{#L!-;z8%x#UIX!|)r)7niIyyIE zqYEDw6lgl0p=RdOSDrJ1(ldsoHWQIrw%41W%;QP&a;|H0P^9HRHvGy9SXJ_5A3VCY^A6%2j_1&D zcgSQtJ@~6gtim{qI(&9eC67>Ck%_aynPXgIw!)}57>`6^g38hJ%Vb#-UD&OA?{9k` zfa(-0C2M$Qs}t^-JaZFq_|sbAjZtZ|j$a1Og&Czs@t$o=*9$1<$!x{DWrv)yV?UDv zhMl)hHOPi%T(Mqa5!u)nIo-uVy@`=$U%Gf%GKSE9i9uC2HU#a})l(Bw!F)UB1 z)Bc3p%kB2X_rLq{ll|%c{!8oC>kHe9`A&Tt-J32MLOJE-s|jJ1GbGY^iV5^F6sKpX zpc_jw3@lwvs9GV!IT!ZfmS*e|GT9L+tU&?I=RUusa|ME<^vo+@)iXQ`@R_pW*J_;=_Ejz{JZIG>yOH}V5MwcU{Q1RgL!hWZ4Xa+B&WQ9bi znC-5f-Yvxh(jVrAC~3L*hz)0xmhz)+U2aZjR*6S)_&74-JhA|AzR0=p%Q2dXQ>LyW zVu?oUt3{nXOH!cO_U9Fm@cT^G%y~?7zDcVwsA7)GKV%}mPz7_&K9fbs`xLKSyX5P4n2zH3Jp$x3e zqgb0DGqV@x)hjBS{B|EaHZ3-|o;G-?lw4VwBBUPwk3&N_#Csq<_^2eW_GNt%#;BGLZRK1*!eyrd<11clOi7a~=QO;~# z!>QPhDs|aNRf#c+h|NQEcPfYHwuSgEj@+KrQE2R}IxRY3+zBg^mIJ`}<(YfoPaoA6 zbM#+Tj-|yVY#pM~A@=hT=IvO{S<4=b&IeNEi5!fzt;>lN*Fl!#u{3Ax;_f?<4?aFO zG$2R@Y#rJ=;d~{l&M-@5I|_%$xpIER-gce3RW`?96~ET6c9tyJ_Lc80i@W*EsBD8S z7X*dvDlHvGF~s1Ldd-lFDWhHN@%~Z&Hk1$Bj zB*Hi2iU19vgm&rRbb#d2J1Irt4lL*;*K6p_x7f3Mcz^%+)qZ;W^6~4Jmk;iCQ>?X? zDpVC3VZ5eMZFJV0GA=MGBGZWIWan@BEQe|Y+n|`fk`m&ILyte~6AkO41Xa2>C*$QRI>C1(zNSsws0`}=%i^N@B zoFOw($dPGI?iSA@=3q7_G|jH=MneZ5!CaGNNHO7-5&E$-Q>#*zn~iaa9%J1d07K)V zYy~sMoFN8K#yOVnG`Q!6%ukZijpZl*r|AjyD3{@=m z9W#n49IX)ZtPRmi!G20K+INoqPU}}$Cj_(D9&5y|uzUEZbE8r})Q9zW!mo;!T%O!1 zj>SVSKJlPj2DTM+{%MS#H5H79GOxtnoVpC&6)-HcZ7iwdb*2M-d$E}#pgQdJ&}QrC zzVdY%FZh-@w$$(;G>P|ww*%U}QZ%&D*O<|ZG8;i0hABbYwS7dV=(uWpXx(uv#JdNr zOUY-PQH6{hSJqMHb>BVA8$OUc4}X_;b#?Hw27RPkB<_?3dc;vEkmJLuOC*uuaf!Xi zQnO1%UVpxR@bf?UKmOnEe*BHKUTt}U#jJ%KLKcLg?52so6xCc>25~A^1*;o8aOE72 z(b}VAB|0#?Xg_&-14mmf#KK2#nhm>WGNd~i|T}g9uP7Uxl5RrWFV!OGyZ5}b|9?B3+e@!nzP5Iy7uPti17$mQ2T%8-@Q+H88Or`e#oRk_bk^Rt!hANh&^;ADtjKz@M0) zvI)Q-&os81Hl8JwNwB2nZOn+x*APd8V&tNXMa!HmO+{agQJ0G<+7-G#j6qnPW{44B(-(C=dVANFRnJaUoLaL<~BT4swqUU8-rZ zjtN6N7USuxg;YuwF+}dfdPAX}8FtrN9wN)&qNL`0W(x!c*;Dai$$l<+TxJfZ6nI5; zl8QoC8M{Rcp$N80=Omq{Z4+tr5%Hy$i;26Y?akbL$>OMW5N-ts;jsr)D@jovzn*P> zkfR()x=LMo#FA@(_Ze#xmfUc{X9k3rN7TC;VH41$t5W23S)@&B#wrV-3#n4cZF9f_ zm7%p6b7mrL@d2~oW)>53gR4OW{<(d6`km*W{ZIaT{ODH&>y_Qzwx`86 zTLJA_elT#j=NU0ZXb6^1pvg#sgN~da=f%#;d<2V>!!xRfnxPIj!%P(M+C_aUJgEJn zj`@(1nkN1oD9LE-U3wvLr2R<8#8Rf*5?LpLsz&q9+lb*;n7K){R_yoJ^h%%MX59+M zkvIfngo4#jD?1gLc~9BXmkdHMIu}f{Tbp$qbLE#%EH5O{w}91Wo`lz9>+#o;?SqL1 zI^qCmLuE@|>21OdQ+dk2l`7R#Yp-|fXJq(9=$Gr`y?n;FX?pA8cC zoPz|13Od5TzWRuL(`mAm^l7UK7ysQ{^+X^ORkI5{P;(4?#jY>9e@hl)qQ#d8e{wc%Upb|Pit5H~5r+fOIob6-=rw|$(TSR5Nvi#GMrIH$u z;|V7576Gb~I%;MbJLiVu@WULs7I%TUxJSjyOI_oW>HTmyt&0EZI7WuO!d&?`7Os3c za7(qw5a5izN`WCbeW1VDs*&OJ#JJmYSzrG6^VsgOQC`iD^b@t;`iB}+xEQ~!wZt#B z7FNL@P3V;&h*ya1ST5u)KI5wRm``Jab3C(7!Lav})N_uJf8au6oQ1MdEvu)<-kjXX zSl*@Xo&t81Tla?GMdl4W(s{95j0 zd^8nun;@G0%Dp-m&xSmc1}M(ERNP@Wd|8t89u)DvJcG(7t`!eFc-ICQ2P^0YkDChG zbC7}>M-{#(^+|LTPy$%Y49;YYxw z0v6P21)z4{u{e#m?xiH>sHmz2YssxSLM1|grX`xmK4SM+PX{H-3(=(^UUsq?mW)nn z@2WdjiAY1h1gseOXcQ;jd&Oqo zc&hLBodV?rrJY&(F3FcW9u;7LYGW z1<+-Nl@W|``4-5>r;Q)~(?9>G|Lx!V-XHkx{l&PY)Kr*it8ZvuCe_Y!IR#T>vW`9ez2ayIIqtlk|}W*NfyrAayfCp}!nH-KKRH zzk!)mL$lpGw8C4ZY^W;T61$g+%SWq@*#eEEnu<@Ji4MJRuNT_lf?%8(DO4Eqj1=SQ zT=Md%hausvpV?oC&vx%@)MO3>vJ8e%234?W^dX?*hsU?sy*9jT0Ral75cAacP?e48w7!D@Di8RXFekm^HK00~THFFB=|Pidp`j!jCP z{BB@J1sPWASt&D=vaH-h5h8|4o}aB?1^IA7HDggG)=jySpxZDnNt$ENOtI4R3Vi1a zR&q6~OUaWj9{S@@NV{*Upy|TRqB>ji5=HBX*-9=fFmQFOJqAX^MZ{daVQ--Osk&Eg zU)>qb)M%BgpeVoKPN=moqhsW7FQdYAAu}Shv*?UU?98(AWG?o;UC6Sg=CH(i+eB{( zz$&MS1g-(vCu5v*+QPrIr&XbciuwIn#_${ZR0Re%@`Ks~nh) zTW@BU>WF16&9Bgzm)+HRs@f>6(Zkf6A zF__vg?})bDj~Fu;8mSxP?k|7*oiV-DF``9y|3Dh8_wX_`fwDbg9$yXJd32rT>4H>k zo<#$bjqLKP#I)WKj;+sgT~MvmY${j+MyR=DQuIBU06jwa1#)-a)Pb704kn4u*1Aop zh<;{)%bxkgj4fqVCvH@MqTS&&b5G=J=SCkM-4Y^JqKI-`Z0M%0QXMflsHq9Huqw1G9OloO?WIvZjzrDbrI-0zM!=2N#o`xfT`)q4kMG&J8j3hU-$nju5AjpD3;V?iW(4YHzy~g|)4LV?W8sbVHY=3wM%; z^hjgm1F6$fk1jCHLUZ7*tuk*}?KFmGi!v{44)jJK!l2rrr~55bkxIIWb6;M;Ue*n& zulL>Dp1=6HFMjs>U;X93^d0+~rf*y>l}~Vs^wJCwqcJK$B;n$TyVGReuinEWlyK5& zeP1t7hiVsVz+XHoT&?xvl9Es@#!6(nFqXH+?iVACA{TKo>FaBCFNsk*33ZVwVxGjK z*aLfuGg0MS7;4PB1WKZ3Mswy#T4t_tHZBfv(h*n`q0X@EX-+8!XBI?m@ZvvSgFB~C zCyPtqMnv}1^B3ue!wW9~fj(;eWC8`#mXzQAs*4fT{`G#MP8oCVA{R@uF?zBs8kX`p zcszspRJ#xNhd^|UG!{Q&EYVYsoEQbXF67pWrEei%*#70UjUa)ilD)LV(YVI;uFeD{ zTCxDQ)%Q~MjKK<&l1XN1fOelKca1*P$pkoo24E$*TB%WThij$~X!6MN5-~m!bipiU zXcBRUQlOOS|BJ?I(WV#AZyIV6+`00i%Zu3tl^fDvb4g$4VHkm{VF(d2ep%_0ACcc< zBth1G%{#Tz{KUhx!RLS*#GqH21rDr6j*QorKl(h|>zReiRx?lVrCCSiFhDaQYp!{% z0Pj{)neH>CWWxmn=Tue*zgcywQ7~Dwp=f~3%J3QN=w>BW&$&ukz;^UvXR0D*s7FND z)7l3Vh2qLHCdl%sA7SrdE;Y}9R)RD1%5j}~4}W7o9a4wr{T4>#(ybqlPQ-9=^E&MhzLtWO!644v<=DzkFFHiFH_Wt3gZ{L6Y z{`tL`Vd?FU7Y7Zp2ikqN%g>FQrV)&6IQ7=78AXe zRQ|B`&S;w{;O5#&&o)~QDU4`d%*wMkey+N?Y77l>d?1^bOSIieR>0_xHX z*hA+LE3WvNN>n)2F)Bi3PL@7ul&o7c`k7X&WKPvntwR9AHn%F+M#KHA$|_Wo#!gb- z;IKN)z@Ck2s}g5oH}BZRM#?qic5#O-PHBU{UF>Fe$q@sx+7Mt>R;{KTH+Uzm&sKlAVY?4SD6AJ*sJdH(Qntw2OHgGqoB!ZgI3?W~#@T_2n$6tu5Z>hyx(gkjC^ zUKVAMGHD0W5l;p4JdXVLi6${mL+cTn^>4A8ObK zRWy1XJ7x6jamhI6AM9~BCcFdv3sc|turnU`tw&)tyJ|mVxsxI@9p&>gaE5LYp%&=niB#KK?POEY)#YJxO^5Cq!?r&)QT1VP)5ubum_u;FQ!%H0RwJ13Kq}G$& zPH~8qgD)SHz!F_m@{KW!)#?c1Sm`gTR*Lb56<1Ri#-_!TU7|n4O6Rz@uDdpTx@8ba zv+4&5U50_tW9y|f2`Y03IX5WO+=n&(04S3_>PNc8oF zez|7_&LbbOd=5X2ahiiQqgFY3n0Gcks|-)92CQLl1{qv|@KiE}rjb!@6h$*fYFL9c zKFVyS(xfzvz1j{{Ds0n#o86E$nZkRr*6P|x%Zr_mIiz&po->ZnlPP*5R4Mgfpk`ob zGa|wv1KXg|{SnHnt{&mHDvfsUSz8Dq!oRJhHcflGIQ_=_*nK7~UlAL0h>2yGL1f5j z*H~zq93giy+`oi7c$Oq(dyGPTw_G>S<#|q2oRSmGi}mHNe8FVPT+vW2Ke_)l$O%I& z<$gTmnK>1H!fLJBu`pAV$h(dWY3C%N1J?2!jC}26So7Ml21lh&BOxVjDOxU&8n^95 z)shEvSR?>NK$ca|a|J4*TIFo`H>(L+d+TOk!jM}Dx_ItjcBkH%V?J?hnr)uR_^Na~ z%8?Ek@s=gV9LOfG#Vh8^W%tOWlxD~+5DNPwZTD*9w=N>Zi3GQJP-(Zs*&N!4#Y6+K zCB_6EVe5G-`nVMxeQb@nY`8xe;@6_O7(1odtl4HfAd-}5JxRjQ%mi}UJwL!;K36dr zLo7!4dg-(q)4!w;-{8d*@+VyFvh)^z3sme5m@Jj+61{-Sw|;usAHTVO_~z56uReZy zd%u5#m;%>zU4TQyutSZjaVC?6oz6X*YR?=ClguPt<3pgTOWgL@vvi7v_a~GN+nkQX z`pYtYGvI1$s-vIQS^lQtqmg8Qt4GrnPHjbFL0liiX+?=uS<>i)?e(0{&OeLhFujM7 z)0epF3U)4Pfs2Z+RkQP^@h3O3NybhmVf2I%a+}AgZKM=CCu=H0dcYfzvs1ABmCSic zwh1G&k_#;!ue&m8lG|o_r5&z!hgzdUf>@B7(be$@bsWVc$8zafL00ax=Y~U^(XPQ< z^eW=g9VJVRp;=J02{c6qcHb@-lQ^}XFqahc#I_vgF+-Y@^^ANjZb z{ZBvtOP_4N!oK6SY%4O1dDI1j{f}oi4%;ShHf0YM*9J&G_9L7!R>yMu%khVOrpscI zpK#b?OQ_wsy|aCm0n$AHNOSFY_L8Gxo4XGjeCnf1WdVobIo7lBZ8Ffv(vefGJN#Rt zw>wzK)yDU-wZ-=akJj;f5U zQYkxIaEYkt9XE*Le!pGo`h`FA`+w~>Z(sj!uU{>}E0?8ycU{FMWR;vsq2X>@4U+U> zDAkE;BZoy4lOe}(`QRB2QmkGZWNjAW&wwTD(O&W+vRE%`A3E;ijO&5DjB!iH%!K() zFa}2h6IC)}T3A!@OBnJ%F-wPoCGFD>y)o*cMUkVIbH8GKN4qs?MpLm!M^0`&>7^-C zIYywQ&-SU7{IT9B1(=Q(=^Ter0-chS%&m;1)n>KJ0>@t%3F4p!uoqexqc}tg1vyGL zd%hhL3hrjFkcBDI}H?az@jk|)7jwUuE>tu)(I_Zmd2Pm6(mR(HP>iIOVx1*~;*$q2ZK|8}>9vu~94pyZe)d8M)6;`~V3sYKob4qBLaCVE%I z5y6$Q?;PqV&Cr_KWB*HFxs{rh0uB}a zJJ}PZNzw!62J7u&*{w3hGo32}`hLS!uE>kn>*mqHJubTM6>`6M&*%MXdkjri5h2fm zp{adoT1jJ^aqN~xtz?6%9IOb?rd>wpsLoA1HuV^tuzH#?pnbH(fqs-gV=j_{4e$sq z(TvvkB#-vM1{&ECIWTh6lG8_ETduokM_Ee$7L^HEnJiRlu>wqv3B2(*M=+wy-KX>~ z{mTIW9Ns6?RBsP&D3RG=TX%1yM z9tOePzIb+P12-;63=J=p>n8e{%e6l|zx(?6-B+JJy?goe^dffwu4^s5#RZU-4Y-6j zCj%6;B4<@YbCP$PiwIHeeLlL3;$g0%V>BrF1x1es=(c1dc;;(_3?dj0-1En9C>1aU zS48%<=r$)i%LlDYO)L>k(Ga;={*CClwR>23li8#VPl`_SfsVIdlw4P7A`TdHaVHj7xDppjvsfh>^-jmpm^oQz!L zTGy^rS6Ej#JS#Qi(^?gf<^sR)tdIqFQ(%egxcQcvC`?r31+OmHXU|W6^Zn0%?_c@% zZvVuelzsoq@6UJ>uHewAjdlbl0fA1LV`$#}Z|}x>2idzme!0}c$*^fin@GHy1)p)f z=kYuG3YozHN8dQY0*3=^(2#~)wMDlam>m7o=pK8YG8`Tya;NXEx~^*02FiGdj^T70 zmV1#}JK!+MN@W3iQ=0oT_NnoH$m9T~P-+2nE4Jk&O&y!I9bN3<|-DE@9=QJ(c)?oaMd@A{i2VXa*(JA@ZW$2?8T81Mp_u)kv3hj~i=3I5q z+S(&qU48CXm5MMpbir-V%W1(c%cf{AfrQOr++s6%&eJ=F@&;E>lk;DropI}TgvG4p z0SS`jm#WieW*ID<@`OYsDX_A^>(=@e#~jYyc>lp3jA@v|8e9%y}(^1d=xISxr>g$;M^ z6~QEFg%8Sbd^$5S%dG`UW%<_n`eIICDc>2ufc-jSTZ^Hkox184Ml)Gm=!dJdZBT zp_q4MUr+YEaUiHBcAyKBH6N?gwZu#|qlOw->wyl*HEHhhV(wew^CgzppBy5X_3MmJER2OkW2;sSPEAxwdz*%Lj1nnG-cWB5jw4i%&Lu$=>tMm6rV@@O?lG6rM8SH-<7G6J3rbV?jh#*UfB`_PkK@$$N1|#N%_*3S5QH(JM6uLv5l@e?2In*_Rrc7Ef z#40BbF1SM@?8z)XM$I}UNXo(Hu=rej4P{G#lnLmJi_$D#OeljR3rUTeS%wi6@5M8? zsD6j{LWyVch6xOt8QgM|v4S+|rHFdQxWvPl zeSVIK;9JaHiG{4wLE*tyV{V87%ji*bkxLh9aPG2GvuP1L3f~H|>vmJT`{slEqkr<3 z{`LR#`=>XyKVGk1`HpMQDOl3#9-aEnVQ?(&b0%cDAJEj+N&5gq51)dR!be94 zUawH%;OL1#RSx9KMBmsuJ5FhR2J)g8lv2E+YLzj9b%&FmdxJMKS!^nlLsS&U3AwbL$4Z{}oC|0-`x%9Ota%?c z5S<)MyoMk9@$oe^(-nsjya24#_+x^bxkYJg@eD7_ov^lh+u@aM#L`hqVcYMSAXR5+B6^;Nq9qayMUhy*~f$XP>@$ zw%__+biGn6Ur-~fjQTqiHJJu9m&t9TKo1;W6eG&GEiBtwIloj|oLosFU;=IBj^~ncEJi;>>UbV1X@eIJ! zTc~%Es-TOnsG+TbPdiAa6u!6?_ZcQCqF2v2rZ%VeDe16VN6Y8DaxhY4qu0<9`P)JE z_LTc`zNTdwW0agWOlzPAsEsjxl+oob_K*bvkg=1_Xer^wyj944)3#t&gsVWgJRAg!6h$~gKN-fb5 z-^dldb3qibpV9brl!`hG(HCX(ZoEd5ih zlg!zvbV~%J*h}^_U>r2(uNk5q9Obo&CM~Pbi|R|b`_(o@q#M=|6A&o()5=ccHdMNqcn?H0l)7tML~ix_>m74b#g6gu}J{+$440?@K@%L82FerPooLWDr2 zkSeFBni<+l?=O0Pd;jp&^H+cSlIP|#o!Kw+>%UtEqIHMmNa2o9)5;O zu7xO;N7_Wx*G0nveX#NMS>e9wQ3cGH`*OHtBREms-As6=u7Uj*H;GmPVZmCQ>L??t zqd!A{_f@@8RLn}tJc?p5EqwZz4#LCJT0l6L*wDH)JGB5{k*UiuP=oI!U2zqG3nZWXs!SctWJU9M$R65U0eu5g@uXy#O{2(F2m%0(MgJi*yW z079;FV}xE33NPjvn0Ls7#)9%CYhj651{%egWR$#XruAgVTrTu%3*Ez(x(y^W%g8&V zsCE+?WpOT~p^L?1LvR)1D|R#PrputOVRqlY<=_9K|Ji@He&G-AE!Vo4uUl>o;-zWJ z_yV$#BdhX5p~ljcYwH+6>t08L4BR#QA{`jPqgd;PxkNVTNJuXjVDy=a$v7GjtfU&b zSiYSap^lX`Ur|~~nS|-IXrJ!agZt8Eq$B;Nrxkf@%L9T5-`Y+Ot_Y5x0V&k|6e?S@ z+fnCR9`x8DYISJ}Rgci=B zZk`f(jP#gv7D(3Gu99W2CjiL=8=w6EKl5`Re&aVk-+yC4H(sel7Ve@*Fpdoy+=GD{ zwn7RM9J46jD>_N>jeDYFy_;RijU0!X8I4P2fW-&Eud+uL(Ld-Q|INRb@cptNrq@gb z3iF{=8(7^{Sst0;RuF+0N#?{wN2ZQg;vaZw#|e@})S+I_=y=cSpI1i6t#Nfuva?1y zhZ@B+LoE@_hmQe*m0wO6*_4h#C+j#f?ICM<;4Pc;*P3iu8Zix_iOVOhP8)*HZ4D%1 z=Z#M-tx_VtN*x04Xlcq8ZbNYy#^AN2pQELWMTP(n2U*X+A;2E7J<9D@IST6wLJj}u zMr_>;?dVKyY(PW>n$$%i*{xu$_ULu@oAPhJ;sLudiBobpmmB!BVS9o0p(?&8o$Yoe zm$B|0zf{f@l_ua6KUBFqi_wgpRurAU$xg{?ch2EJ+}Q=FLRSL`$OPpFz8Zw4r;?(E z2myuBMJX9NjC$Dq-3-$^oVcGn%Mu!z8%jPma&D?3D^#j+rzgQBB+#uUZ;6HEC}Kpw z!=iWfW;TRBuy)_Nct?qdm9{>s*qrdOp{{_?gS^)Y^6cdhQN!NH>r5eH{A}(++xSos zL05Y-)Ma3*kc+`G9&p8Her%t_MCUWWIgwD_r&ZNBdjy47yjC1r#V2$Wru=QwwXxT29JF2jBp>eGSz&|`F$ zp>SMwWGzELbY5NY!C!;p1ywh@b2aQBA4E-bz2&dNINv@ z?qaglgHaT#QfZW7l8Z7WLEuo0#|U>BAwh_ee5RPRGHY=tZvud>i)(P|7$w4g;eLnjepusHXnK3K$e= z#Gbln7gx=u*0-tZ(UKV34M81nOu6Se0Rj%b9*wG7?=}(nr48SX+ zyGCLaK9xaT@r5s%*y#dBK;SKVytG3xQV_ZJRtd7cl91yIaHO}IC3_C+Ufavdbu-+) z|MKyh-+O=im;Q_I{)vCi@cNBEzlc8L_IkfKTKDPbRb0bvr5VAHO}ab-{=-o8aL|5-ZXaC_Q;8%djHgPcfcz|&JMOS ze9$Qy)7I;~e^%i19*Fv#J7_4cbH%q^-@pU*3nNMlgE#K<_c!p#(G0*-fB!(d8s^mb+WlLm??cu- zCM4dT&WF|b6ao&RZ{6IjS`s<95H-Os*ha7dupQWXEg0_iyIt20es+KT^{c=B7qM(# zZ`OY1J!qY)hFf|vj zu5Qo7=G-1rjaI`jjI69&oCNC09=)%&Lpm#NLo6Vp^JKX0j-{(lxs;T-t3PW{W`ZdAOfRd&O_GHi(LysI=;yImc&pc2xc|@x?tv$0WPZ*4XZAj z;ZJVc2*(F*1@T3N@FkJgWCq$r^`b?R;?g?{>Ty1!a)5vIS*_YQOK8CkTURBd&btG2 zW3r32no&mzu1tsqW5=s%cKSV?D#$hfNJt!5a>3f9tNm!hmO7B#k^;Wl5kIhN*9t(E$#LxXU} zL@4LEHO}$TujsHC9fH8-W94EnbjVV&kf9q%vf?@dHc^CU5nWWvV92!~ddc1?d`ia) zr*2Mtbz%KKb0k}g%j9D@`wwx#1}dK}6XujFF3#=EL|SMYbbn!HV_11|7bUnWtBviv>VBvs;Oid<`?ddQrRqp#BodPswQ|pZ=)Q` zX;gkY^^vV{RO!`-)*dVN0wq$8fp!^jJn1jFkZ>nzc`@FeB+R%P;?P2DGt^_!>U_F#3%!I96a*Qp z%!Wt92aR_$Y4XUSet==ivEGgM-Z>c$E^GL=I@Uh6RN}?G#59lp9-f7!2I@+1dLBoQNChZ_&9Rp!G4BRarI z^-<`9{vc)@A8@JfaqwN~A0B?-x+*V|1}wZiijR8TZ@2IM%v#LT5suuQmV(pkk^(s z3#GWY+N5Qf^GV-9aINJ^JTe-~+V|%Or#a%Qd`R4N5;`y=ojZ?oAd%uN`=LJQEbFQ6 z$ZZok@FJGK$4SN&y2}xMIk&?YfWxR7Nd_(KRuV~BBsaG#H`VAe0(#KRuA0k5vr zeGuh+AWRy-lo^vL&5NC9oCm9h2xE#-WSoLz-d2{wkiX99a_wfzS-FjsWA%yFND3>xbiF=hc#^g0T}I6h?JS|}M;7}j zu6g59Uuf^@0B&<|7BfsT!;Q{Or-p43k!Z$V|K+cIp0pbs>14|Of%4I;cUqIc4!b&{ z&{|mnFw3|s!!X16^0-6pj{4e$;F&KV!3o*Mgo-9z6TPH$j?+@13xub##0tilWo025Ji@Rt0bcsYvs2N)dc~H%|YltOk@NT-rrU$BbZccwS>CWRP^6eL5cX znv1E_vVt+laTQNKIOgM9Iousj)ACA09Biu7llqW%8tcd80M{&CFrt{bq_s@F^Kw9C zmYbnAHrCuM3g$;NpQZ}7gmk4d_u_lBjGp=MNY=nJ}wH)Skat;ijw zA!3)3PYblU92J9!N-d2pFtq1&^wRt1?s1Og=}+oLiW(zYNnDmH<02q%jcsi`@yrcS zb$EDqQXQ-u5SUsp?a**5p|fyd`OYk&g1aS%%iY}6BRE+Wu=gH9u0(~Kdw|CQs$S8p}L01tg*j8n@m|38@HC)!*t7xwfG|scoPrl%0u-E?S zcfa~{{n`KK`~UbKe_5|(Cp0CzMAGbP+PfiVmS}OnD&f8(xv26gtw;TyAN@vkUfzK< zHL8gDPXDEHaHk&fC+n&%nS?ec)X%NsnQ>&pP6|vXtRk* zOMFE7V=SQy2TnS$)Pot_FR^6WS{s`VEA_^(<);>+XrZ+UTi0uQS>OG+mxXWs*T3>z z{YK?rzFG#$h@n7*T2}OSsXT^NFt$7t8G?`!OA%+R~XW?1PPM zIqk2xQ>k*a^-i_2^N8i6zqAG@QrqO#A5mykeNkEYTyu24lsV9x(-{r1EmC!91<|>G zYw#~?p{QqV2t69{a9o}~XDf{E9NyGqF^aBq!1b*dc^0GPu6x&5XJSH;s$&reOQVtY z1kGp+Sx3zF+#(ck%+EU_&8bdgkB-F+;bBFAis1V+xWdISyt=Z6B*uNH!9{ds>ua3Y zckK)}Ya3l!DOZ2W3yFZj2qNg^uymL(afCUhDbYk(J3wG~Ww=1oy=+tc1B1YnojFzL z@mq3wmXn+3b$aF|C8DiVS)Vs^l=e>(ICLdFCXRUXWKj8h)+F{A7#qY)=NTGor}NuM zeHY8sqtp`Knxa_Ga~+S(;H5Y9EG`do4YpLSvaq;g-^^T0t8CQ>E~NLl?|u@xuP^`j zcN&flKdw1lvP~A(>rSYSH~NhH6@<^0L+85X3Xd$@_`6gnOZh#NQ;pQacu%%c`@R$5 zrJM-I%xs(R$0idU4EREfE@%>q4UU~DyPM5t^hIm+W08|4u!<)>(c3L-J#)gfAeQF- z7?Apg5qkim?O_Oh6PR2Dlmj*l6-qXpWkyVtp=kz;ZP=2^tSj5I^oY)bmK^>WT;$VI zfm&^+8x*2Twlnn(*&eB8c^&dr6WQAjWN8eJ9Zapy>~(9@c(w#?*@QVANWs5_G7P?c zs-lKob@;}?eG$|wq%^nfNx_inz{j!aTB$K+U7+Y$B1y+629Zd_L^_=Y+enl|QK1oo zgd_(ZL=Z)cM}h|%Py7S?Q}kRAa^N?i6$uyw+az}P?%p43t-0o$?{nAiplVdr{qF9D z-K+PS^L?M^{b+@Paod{@%i!3&0n(qxa>*4P$5Ri;RR@ZKwd`ohZ(OclAY_Y z1Lw7pE|Q!at%om5byPa5un#jB3rw3o16tJs3^U-Il<;}hfE6lL16~gf+4pdOFDl}a zLay!B9i9-n}j$pxZzdSPywt>nN+>MI@|+Y9A*`BxKPw_yZZkuJIA8cj4@;uv*!34h#rX-qu(K zj-O&ahX4vS7*37VWz|+W=&ZSF8fNGrBB9x)DLDz?z%ASogvJ6-JJn({y8>6li~Du( z!M^%_eE95}Z+`e^|LQOOJAdQ#`+xME**lBZ^Kegx(-k;+$_kHp3)2+|*wL56AXUFL zMfa8ubC3O*xx_E&gApAHR(XR~=o?*M=5x zGdgB)HGfDaypp_Q%Jude-8s}#@{cfVNMn66RoJ#;$9@Yw)#umk)K!L~y7?VQpWd@> zpf>h$GH$%}O!72>eq)F9^3q33*V#s$UN&rOa|Nuu3VgoYnBLHsh9pH%}dyH$0Tv`%4m7>qYW;%RAZ@%TDYcSX%AzFfkG?jm<&~J zM>2{I)Nx4&z2I`*M&@J0glGDAtuaYu0wzaau2{{l*&FH{K9R6C@MmS1j-s$Cn5e#+ zvnq(fxP>4%t~}u-Ew1yJvYN`PoZ<*8AvZJ-cHq&aChJpTaup`#r@Yb?qqX;trO1Ts4*>lt=jnr&?{)o!S% z?{1wE7n)C=!s)0WH(;z z=%C~WP*x}`o}BeRQ2|B%$GPX}Wf!&{6v7YE!BSAY{PKx7ehO8c+fZ;|LC1`^0_Oc^ zjH&!g)7(`m++N)RNfEh5An#-WUoV% z+wv!SSve0B-dnv_+s|?gZ_Aav`=5# zhu@3$zZaiAB$Vo$7sny&X%=;8ulMWGhqf|snjwmvOL`V>TFhi&tQRRtMXb7hkF%^C znk7GWI?rKFVp&*AZ#+vg56opGzI2!lcQ3ec&Lj^kqLT80nEth9TM31&N)UeSFcXa?LPjoDt$R_@Tp zoiIOOTph(EK@?Kw5$zUrA;s!|;%VG9qn|26b6$QunN!Vfa=Le>?9&fF{*gcUSO3HF z%l}%$^9w#B9Kf;55tkif|C1NIC47dB!;mSriz%*NF1cHZRTs)|P(P2L^YwnkJg;Pc*^r)iI^`yE#H zUaLkQ)SY-M{D4iHe;tQf(@NX9BSY`9_?hq}dxO;Oo9}EWlt!-@Lv}Yd3&7DO?Lxs% z{#GBF?-(@L{^G!%Mk^QL2Em4UG;@bph97rBVLI&KI$`GaFg)?@<2j!{{PQm!zx}`b z58wOj)p6{4HGhf6!`l6AUZy*9M98Oljnvr%Q}1?L9pZB!`D760M$lvOV;;Ul;m*d3 z?HsroxIK~DB`6fsk<=$ETTC*GqN?It=TGOmxG8T2YFD>7vvkzgWqP*Jmc@=_@?(P- zDF)+>7*G+ORf&tAv{p}sxiNvy%4Os&!;Kb~kfNOFMQ1@p^2x#`RrhgwmwPzn2N`DD z4?#=K3@0@@XLaEh^Vz>GmU*@}gErxZU2gezU3y)j%7ZC>ftzra81* z>6Xz#KL|E7%U$%9op!B<(HLGC$de-qFZ%O3T;3*>fHM5v%HAx)#Lo&SH5hkVds_U*$d!QN?3=0`O)(R zd28;J2jl9}*+|dg9zJ1hJWkRqP;|-Zq*lNW2Gl-pi{= z%VsK&Es*1-IPpp$>V|E`UM-6kmTHG9+=zo&Z&|1V@?VMsHUYonQb*)kO+~#q+OA!i z)g47$-=aiPxUsB>-Se;LsfXoQ(-%R*hoX)$KS z4U)P4*9Uo*H+?EmY0Q zFEs-Bhk|jdt7wVNu9VSjCL1g<-6zvwGhWN}BPG zY8#C`kNdOZX5rWgd5VqOyDAhdbCVb2b`eI@-D2Ib-M8z`=Z`AbYp2WOP1<}9j8VA;i24s5>6E*=BkiU$_B-)^=5E@)Wwg>eg; z9gJFL12PM9<|4?zANr#oe)9SE|Neh-ef|agWPy!CMV50lVRDIXO|PoWG$Lh=DzrB! z2}$rLV8sx!W!pkC)SOTmKS`FST{Wd_Ib|O3t^wxh>J>tKp7TAjE0)!AtJY1w=5Xt$ zN%$)tCu+>=&B+(>gsWmV3R0ZI&m9a4#ck+ily`aY%UWI0z+o>+OSU#$0-1h%x`&jL zl@iSEFWA|z%d|RPNO2|4sVM^N27D5v?&ekRW_H6fI%Y|o=w&YONgH>c@wmxTq@zV1 z-~k^4U39(eGd&)}dMwThZAi-7E$Hfo^z1BDa7rNso2(GKiA?Gas&J`1CoT?F263I6 zW1j`J(yNnK$lC}xp5(TC3s#HzKdaqaHn=PkcrC_a+^F^LXosT|aFZF}U38^1jnYS~ zV?+b(IytrHWQpy$rSMGz$uc*nkxD)46;IM=Xji`^s?_b+9hc!b<;GPUX{-emS+*rr zzCwj+nu|ebLac%Ml+mDYI@YG@5|P&?c?Um)UD3wnQip2Dvo{);ElWv=zZVwqY95q(Q>;);xUS0@OP}b%5ZT8uhUYSlUe-o@ zv*BWN4IS4CbEBB=>OVF5t9|Fb&;z@tLrvPCTwIOA1*QrKuumh$M(-0t2`VeKY6F(= z#u>&dG8t_Ja~-=vFEA^MCzv{5`mrJJvX?2#^ps~zTt=Lhtk!6pu(WJeI;QZR33)Rcj6@l{!)At%@pr*oAUY7p5s0$wGm5Qe51h};-#tItH$VCG z~qFtyhe^U8yywLezmM zi%C?7@RbVZ?EBW}@vJ$P7nLvf+U9ML$n6FtXK46jiY;QYW?<=$@pGtz;bcOI1$EMm z5-6XnV%Mea3>i%2(>QQrM6jBmsh>NCoOT^EQT?h>4ePOCrC5ZW<9>=6#fW%5+nSEZ zyrvdjC7hlSmTD3unnj1@c=OxKmgganb~36mV%R+EQkFXbCui(MYB9TnP)U+}ikPaV zM(I6tct&)jQjq4y56vRTOA8RDnsqbAQ|_8=_qsq#4wOsI&uevX(}3yx_Z7Bq7Om%Z_12 z`UKAxP8O4#-rX0r@V*Nj>PPG}pS25g7h+`>aZyGWV_6AV?BB~d_10Hqm%l8XVslLL zI4zFE@@R1ezrD(&Q@f?5JyZ?5h1{-r@xlVxuJ3C$(8s{F3vz+%SAaUN zNp?Lmt{*#Be!?SKa4Yp~{RrG9w7j`X+pma)ewaDDja6~J57iPj8Q}TG!X3*$yxO`p zf7{*~$2*eMY+ZBSkYiLXEGLdOuS6^JaWj{1Nq6%) zdZFnUxFuG4K|<=SsKAs4^!YSPq93K6m&)bX1}=*(@ZeGM->9+vl;7EcRsp__Q#SC| z4OHn&;dBEPA#+iV)H)g1C{$~2x+*o<)=-MN8m!b_Rt)k=j@oG~YYFw4V~5$!5kKiT z58ZS<1Br2QnE|?VXVq)+&~aF0n2O2f0{4L^F#35@c;i z-%@@U?aq4sWAsGX0YA;DiYc8&s1ofW)(=s-XSF;+=Ws0+J_wUiD4niB+#+yMn^|dz zSS%7&3{WLk$I5~+dX9Npy<%h3sLCZ{aJy1<>AETcs@WXIsG|$qIwq%7P~J?mttATq9WO9y5sKpUIJ%m;4#TIa%Qr z6+^2GbjKBVU}TAG+j=drU@aR$eoRqCnDsgb*r zjtN(*pA1*rPMiiqR$9= zD^FqOXq_LF;@--R4)AL`XIv2$E@fd)TN%--XuVRwL^&~NMt8a)a1P@6qxbLr?63XY z-}p~|`uO5A#)H=@-btC@=5Sm>l`rI~PJ*tk`AWaMr_cDL#;6^~TdFOv@X$9zF{J@t zdZImLoc|u3<&F-X*tB`vQg^g7%uA(~QmYpW9FALBENn9oI$%=+&n$?sk;7PV#@Hx( zG^^4}+H~WnspB{rT8{6PJ48TvgI%X{3)mXD>w;?4r;S`bbOJ`3hn2FFsQ<+ex#@i0 zgq`e8$6X(Alf$D7)wiEzgQ>RK4Y}v0LbCJ{`8F4N>vi@p86&siWP}XxgA4w7*Btae z)nu3Vne>D^2y!ep=MIe8DS$Rhx`cb})t=#SCv9;DXh|ZPxv@NkAn=JeUp~Hlf4%-c zzwyP(yhzffv_N3Gch7acGQXqr(_&Upm!PqIHqPAPI+Zv_6*DRZqZw#Iy5w0 zlj;c9D6D6OJF>(V0W@FFJ~6^A!miTw**P-r^nOL@H}h-|iNHiw6u!L26PMKvRUO)W z00L4SgkAF-K^dh@f~q?L&J4yUP90YaDGEs)6Q%1G2`l~0q}wN4#%~UUHn#(6x$eWiY+z;S!Q zcgpD82$X@^)=%_B>{nXfEm>zu|Nl2S+gMk|CYzECAE^V>~SQ+ub29Z7_q zJyF@v&Al_^`Lv#hoTqH$34`;2E@m&K6*oPE&lj`aE?4Mx6TgEvs7k(`M7wwc?q=~= zG=NiI?4rTW4}SG~YODs}nd4aW4Vc3V3}uy~9xM-3es_8}W(C#KFoylUZ~Pdq_& z9kYK{b`P&Y*HPdajdQA_0~oHXSIaG(t;DLU11no-P++}PT$g6N8wODgNM@=@f4oF# z`}P-UQW*$J001BWNklT|cCsfaX+pD{IrV zNb{pqmbPslbjK^9*?BrSw#fT3q9_1q*}7thShP@<3i=k)XxC6N(V=90h6~tXJu_zZ zc)-)o2|NyaZO5dFBRA6WbJpG}6_?v1hxRoa=MI4J4kf1hk((lQ?VMbx&|Eg2sX8@3 z#5?lw{21T56 za>`KNP!w9J@ujN!ZCJ^2BxdDNoAI!SSvLncNMrH0YvonFajG z-~8l%>~H^9=TH2pkFSq+2)EZ)9FGKZf#k6jlTZU3SJwEfJxjQtlra9R-d4>Xb*YT| zhlrX(+PP=;tT@a5qrk#W#W0Y&xS8{UZ`f3Vcl?%O!qy|m7RKfmr5K8u~UnO!*CDsrA;PQniwE6Jz8(; zossL~f?lo2RaEF0&A~SJ4G=M~Bd>bFG#^X^fhqG=kg$q*pS!vY&T}DSbOUwb|8FkC&B7hpEVam#a~=6Bs17@eS_7pVC(z+X^PE*W zw$+ZYY0~V3XNDz`kdrslK0A`e7_s_5-^#c&5%OL7awjZR$FlTXeo(oKn4HT|!iNcD zRc)ByAk)RBR%ltCNX+i?+MQ}s<$b=<-UW%(+vb$=#XGS*AeQZYj9s!fHqUYi=y^>p1! zsKcn}mkNG#HH|u|O3kDKN84#b@9F~59K6~X5Q&&iiInoYy|eaOt2{BW?%jiBb@SNXCL z6(y!MS?NcjUpQNFkMl^c3(SLY;I&%jpE#Z&n#o_RjXj&yX$^)4cq;6NNk6i*sOm1K zuGI^b4I1LQ@=}P_j5T*$E>g!y3(?i!pET3fXyKIE*b9NlK!eS)umNz6*x;f>)J2Z` zq%^ydp^~FTR#=%QR9P~?$mwG41fDU%WlE8pPlr>s1F3T0>YI5RLzR5H=23OIF7C)6 zKdx{!Z`G!oksfx}yE;6*X-Z$r;=KA}?wxRg?(XF9vrdwg&!nQQJ!K9wwxA@`p0e#~ z^#-ghYAP9Nez5lc+Pqu?nUYqVLZzipB}mWswpgT_29YIvZKtA_U0;gZ=w!YuPwfJ} zh}RE4{^r}i{I`Do-~RW*KYQV;%M@$mDcz#y@P9#;aT}kt40*~)H{1eL+KI-Mu)!Or zs7G(!4a$XK#;(4FZ!Rb@CiAV&xe}JL4ESlb;!fT~tTCpBnN=Ao!*I>;*xu`Dxmw5h z;vTEiM^WsEU-Z$wp7Op;?M%|}qtgLJKLHxQ)B{Ou_)-%m`p6=QPu1kOh29F~)`uwL zZc||Ss{uCP)oy;}iQZk2MfX7Cl|_uU?d`~iPT9}zEFAO^$k1+z_M8znXm ztd^XcZF$AhEb&M$P+D>Wb_}<#XK(GpcYFGX+jz6c*qG3G`#C<)v%U-M-ceSJo7v^r z<$T(4yX^7!gFnaL`JI>l`|mtHf9LjamgTMEn8lqL%zp6x~y}xsch+~cfOXC=? zwtJXm{+=?XTb19wp^7hvLMvZhCzUA=66{qMAhN|*$u=wt+ZFHZeG1JBEWvj{ADI83Yw~nkifMFlqef@Ko``e2!Ka-UOgP!koeuqM# z8*^LiSYIi)Ij+N+$8xfB{EXMS@~c}MbZUjDdMf7_r?)W+*f~`=sa)g$3)Z*rI<3@V z7HzI5&=XXj zwpX=-QQ3}bOUY#<^oM<*+LwuO?W01uTJ?(=ak+Yw?z8ykVD^dvj787+SejN-O_Y=y znq{C*^^AOMCGLfD=rRW)$y*xy`N6MzPdKHeYi-%Odp2pP53nrS+TEpv-y7!0 z>$wz)=$zYQhzW@{wFX-Zvx$FozM{gk^32*KdWrE{q|iqh3>K2sYqToQl5Na$lkr>g z+r7;sXFNVPhg!c4@(C<5JF-`=KuzU)v%#AQEJ>EI-g_h zl5BbFEdry;A#}2tPWim|N zhoV+ImT;OB>AtQ{f;0xZWj4RQ#{f~l?ennk*H8HP{`uY4AHVt0`;Q;O?eRE|Sv~SBy^0c(d7VGeL{a5uXC|qaF z+;bRS9uuQd6sW({A9K|-uAwZOPT{jsrvk`Z4T#C+>^aPYsWybY7dnR@clc`OGt!;w z`#R>6a@_r3*$A`GrB@l6HYKWq5@MM*y)S{)WOI>~a5B=!Eang3=cJT<)<3p57fhLU z<$+iEo&g`Y_{}%3zxbd1*ZgCDk{8~^tN9Bj9-`2c`qtRe$gceIw5Hv8(SvVF;O!gk zwD-*?+Q(4dO_R63y=!7MxcW*#{ zq}M=O3s?_xTVya9#ir<&36;8i&kn{_sBpITWqD;!vSXWy+F%DWoF0P_{N&`jufG58 z`sr`{pI`7tJcK`tp>*0)C}FL-bKyH4fAh1hsle% zFPgEUR-}iNI8l8q7eIKQv6aS#zN?yBOnZx0Rdf+|AKyJXc(Dvo{1JU@f4lZDg9xcK zn&_FT+GN;JN;y{2bl4%{^YU>(S*_!1PR%W`{fHfDK##9&l+PvjQQC-cVf$uZS#FV1 zcUcyw6pz|5^sk!Mc%~?#AwguDacYu_Ri1I3lPg39O}d$ox$HF@{tQ$MZ%Aenw_7F4 z7#`NKKhV%G-0UzTsCG4dL^-vR9wyiW70Ekuue6|(qF?LO9%EgZ94QW32$;wl$O03+ zST1Qsea;v@S`fEjYKcs}Q<2uDn1yZ+t6iXC(U)+`9<0^FZ2}dczFPCsx_EHmJZN5{ z8zMDs7%mhR%4~c$9O2n0@)kRxl`VZ5N%wh49THv_<(@u%qi0PF)JiA5+L=Ab38BW7 zqwNz)+A|ZVThBfxjT&L^=%CRql%!!c>IDqUHWGX8{mKsP6|G#i)45DJ)8MQPJlI=) z+Wqq!+dF0U)y_QM>=&%<(@j!I4rb_Q%)r!#OW6@7}$@8TKTf%r;QI?(Q8|nZZgb?Hz8BVYBV};+23S z0FUDaME=Wj>mn1`;V(gv;~4FV%jpq}g16E86oj7JtPpA1F+@CBV|plX$Q{a|fWe(U zq4n?_3)>AH=W}k8|EA8z;I$9!B@<-&W`gl5R7<3FfTzTbLLt_xo8@tJ4MLItzs0jY z93!F#Bd>Tq+c_NM9jlt51x9mTmnQtJS!<{+F#wnn%GV*lmL6oYRm$j{P{6vZRV!O2 z7*)5+wK!C35{d{Y+d`q1m6?zw)e})z6)6yyTgh(65Xl6cIOHV3RzTzkEcd2@c zajhLV`d%*BY&O6n&jwN2eFGR3i7a%ed8foyU0Uf~h=|U5$mNs;vme`NjqKec!zm39 z;@j76z-NS8)b z<=4HkV|OMrNCgIEufF2?kmPO3dJAGIf4H~Q0JN>u+!@&-Cv4O>>sv+UHaltd!1M5F z|MZ`WfA%ka{lWe9+t&-1zoUVErA-Xj8C5;WIP1VRz|&gf*owrqSDl8wjjj^2XeUds z^psI58#_JoFHn`v6^mMIm5~HkIfuKe?q(4JN3+EZgTFFy>aS~VLMqh2TjveBI}(vW z%Y?a7g>rtAfS5sBoB6{UQscf<8(td5meH2ZoTGD0JPmq0D1~!XJM4*u9iI()4A`)^ z?Aql76y`K=S;jTdB=rEso}%+sba5K_b$nn+e~*&Dc;8qFXC_s=eYWkj6NT- z_H=CaC5;#QpeSdNcsF*V@QO!+(PKVpnmuM>2sEt~v8|BGQD6p84FL%g^3+G)F*+=e zb+&TSd=z{Ay@B}4E!b~$`oKGI&P4~kboQWdG&I9wP2E#axirto2A!;ci67<_tKP1c zN8PfJw&K*d!{ldl?0gnZUim7B+!mQki-wF~$=^*wT*Q*x=Y#FOCwbF&eO}rT!lF2y zf$=8avP6QYO+tgDH_Im zB`RYDGEqDUda3K{$hMeg?)Es7`&m~wOZSxJhJki2!z<0~<%Rb0@oW3ZZ-4ib-}>(T z4?jLXfOz;hwf0VMk{`H*MI;3`k+?+WpGUBo+?oHHkRS_;gX>Vf4t}P4sLh z%ma@{n7v%~`toP&SN{sW_jB)Fb4>AaSZ`h!rwUCTgq&xwY^Y;#X4`TKi!9m>C-ZW6 zy`)&W(5YaCnVJ#mFAJ~9b?)nwAW76)j*v}&7*w;I#e;laW=`v3PqBbgyx(}kW?Kp? z8x9C)ceOTGiiHk^tM;=+pQ$Bj_g!q1V7yJ8d2_d;k3lhQoM}bSu_CRN`A1dOoS>(B z0F;OT#RFi$j0^^UNAmUc{mUQxxqsu|`_6ySK7AmsDkiRRi`%)xhJ~3Se5DuX0uyAg zBG0lg>q|sC3#$fBWUgi*-KW#A&WoPrt$F9n154|2)M1Z$V=4?oC-_YWmv>-7A9uin zIlH8hW+U^575sJ-_ipkrQkdf^BQ7NVy81+{#J#jV(eozlh*Ai?b-0xRLw7Cc=R zhNLwiXPh>&#u7w08(kJ!2U-?&+07>8YHE}ch35I7iZGE_zHD9;LMwRO%0`z4XIm?Nfc!{Eb>KxHbZ>(Yf^`shr$}4K7Yq4#mo)5UH&%!|fE4S-toemyax7 zeqczlak&UOYRF3gX&rHl-NLV6^fj0EyTqmSmR>XP+&ncdqZjUW0-Z79b$SInGKgx_ zRR_c1#!C?V)=(-ytdD65L262P1yivuu4<<(nXASaQye=}MsMC|E*5KbVW#o0LO-N6 zz{I>LvE8ew=N;t{;Wr<@ea7>+y-O7=V0NBlz|Qdk{RMe4 z4U5S*ynr%oiq4pNbwn8)>C-;0NMxB#3GxJAQzcxv3%ep2l zPws6{7z6Hfs_hvy14i`XYkm9_8gYW%eXOm{82Tm6Dm$#V@%cO1oWH<2q(OuQ>C5wM$Ht z$_RuYvt`*k`EmydJ2|!BM30o{E{qN`CGXYB&fdcgYYkaY-HhkBm7rZVL&@;70M4;P z<{lICJ1tPxD+8>G83Wf~xH5>!klwCWU@V_-dSFY47wx(quUl`~mP+|eoa_QJ?@-#s?zZX+_!ekO zFXmmVo3NTblDk!THQ3k&k1}M(_7SxIKv!!r>hLq%suJi<_%3e5Q6tt3*oI7>D}k2c zt*FY@E8mij?A}jQA9I`y9jR0(vc7=j?Z);j-x$#T29zaONKc9id;z<;3YvMf5MAuJ z6<@u8S#y+Rt=@XWF3V1ZHh;F0$hP=&8XOcp%d{~K!>nnHE2`uK3Hy#OpMU=keE$Fb z#`8D-?z``OW(8qpS@?p$Rko%U`eE5ktSxMy!P4_`btaSy^kXE(NH<+NSab{z-@X75 zVojxMFB+Yr$2AfB(MUPF@!qinZhjl+v6DgBTF_3WL^J_c6zWd9=+EW_&#p0an`R?s z?y?fk`cBw{q~q={urj`uG&Bp6s@X~j)cV`t5=G8fTcr%4uvWL;H~A8}4|#VRIR$-Ol@?MZtGnB~1^ zxGkGHjstGNkhg=)Z0iAj$EK#E{uy?B#KTt<_lH<^jy~Ju`SRqLz1WZu?UX@FYsb#e zzj`+RCALKa`0k_$T5+VAQB^+;XAVlE5jpj0r1BxfaT zrq@@LTGa+C`J8RF@p>DJta}LsgmDXG6VB>D*@ZQ%&hBYEF}y zg>w*Ub5c!_dYT_=voUf1x3(%Ql6fTp`;lzk7bY~e|LLTHwa$P;Uo7kAcogZA)zd*E zQi5cAOAhN6?rPb6s_*L&pq=v!f4=AYAAS1zrypOxv4=hIcyT}E6?Wl7E^CfQR;XDv zczLmy2*PK+F?pS12;Qr+h`> z>9b2j&3D5F(c$LWTUnV=D4vYN%h1c?eICyOqq3p&2!p7C4r;}onof2PC-q#QU2ykh zqDgspEu$ubZ%fMDbNS1?J&L#CuuQTQNf93Tf~b;+gkYB`%%k$lez zZ>tHG*IA$ztGJ|jsxj+8cydQSgXi?{vg<`@oM7n&r--wKUjs=E_2x*CwiJO;r1{Jt zs)PVha(R~wu-vd!Zt+>V)v5rqn2prZ2Z(48fTblnNnROS?06_q4yisFU=8L}2lI%? zi9g&=J>_tJlw2#WN{*(Yz`SsVjUWUFc$gg=g2(LO@^gOcyYK(;fBoOZKmQl_8t%t= z9Xu6C^}s%Wz*i|#jcoEx2)O0GOc9~*sBP(8c3a#CQH4zFHOE&@4VEp`x-Ecv2*>t8 zSp{>oP<9c@_a9SfcpJiYFj-AKoz+=`Euhy>ly-U(@H537@3{uPa2nW@<(W@+AqLx$ z@?8i6LsAhmeIgOi=;6uf7|`%C(&CmoC4R;VKgQ$|Em-x-r$!j6-o05Dh%-mVU@v!31@(vK(L>|8=A7-srVX`oAbhGqOU6|*x8sw7J<zL;&m8z*Ru!$HBVatgI8l_y+Z6ckNA}boa4_fI6GY$=1ilIVPwq-)j1GOT% zQmdR7BpQFgD^UN1R?pTmtw7_cC*XKPN|Sq6%iIqj-JW2waVZYcM5lrupNJ)4UuIC;Gz4Z7RPpEjPMS?!!FpU^M2I&TgN2bveQ zF?bobdqE9~ldp84Y%s@_t{-!+v8Oxo>5V0v%HimxLL(DlTYVgv1FSY~q^jGB001BW zNkl&0~5Ye_gUKq0bUH zgbI%_?_h3i^Vem=0_hf!O9XP+Me|3$L)xGxVK?n-?XyDa;~h30x13?NAbqH8ed3}J zzhXpVCkw1MghiifN>k(Mqq9@2>nvTU7<@uqC|O{+`sdmeaYgCL(ZOgjRMLAho4ZZy zb5v4~Kzm6Ybc^WA2GDUGH!5N~ZMkmna?8}L!0lLpvinTe*5v_6ES6j;J8*om6P>k1PK zY`pTEqyWk7!V3rlSOxiJE$t@kr{k`1Bx3G4eNRP8f9|RVFj$(@xBy^4pTBatRXJI% zyo4q@@rco#l;F|}nsC0DaRW9U6%wFCK>Q5{+-v%|V;8CwnM+*g3(mo@y{?wf>7p@5 zedO#TIGhnh1w*baXwZ}*DhP9TkZ}qtI$&A}HoIcUZiV;h&vBrLjtVaDwClp_V^G_- z?jR7!w-*pA^m=t&eO%#Bk`l@+gHu!bn%UOiYXG?;ve2i7(j>^Ruq*o%*`Wj(aao2T z=_aufi-^fNtchcGR=gq&Gshc^a+a6H=ea9KZrCue8;XLH;a8A|!B9;}8c5gC6UK|j zjqmvU_LcwWpZtq|;;;V=e({IQt|tv<4~y{jfys%AHPM>5-X7SkY#-dXyZs*;t=k%4 z^&WN`54TaP^`H0|w({arY#Qc5K`HbF*eO4XMt>96v(tL9$ijFN@Pk?KS6YZ|W|r`Cb^XU18fD1FI?WN=mX#=Ni-{Wv__Ts>zb(hn>6YB00Apl(LB`CAfthE1*?G*c)9A)3Wn(qj^!op*BM@ z;AVcRLP+z1PY`xJ4}|?b|3jAdhBy1}E$RA3BgaZDtG}`Zr>t|)`qkCrYSsyt$F;+d18df-+|4Y0N;@}iHo?@dEmG79vv_IG46%G1VS_hD&F7}ro#@>z=%$FTw`n#EyJAwZw(nXhMQv4XbmfC+ zyb>E%lX>8?)G3kS&52;`9>>R@k}ZNhbEd$jMEE%x&5QM_wv9V+vjc5|lj`y?26@T3 zD)K|a_o}z%Ri99GWy<=mrxg&l=cR7Zvc7~wUU}HXBaQWEw!L!)Lj>o+YQ>Ap23j<; z9Tj|lX|>H&FMZw!tGW7ffacwqizw~>jQ3s`WdeVZG?;Pck;9~TdC0wGYTIH|siF-8K^Qv3szLd*3mxd<%EGS;LvKl&9 zVN6P&3$x=Tg2J)mouy%RETT5QFnhh~4R>_`GEWcM0o%K~Y*bRtu-b#A;PbYbLiv9grl!KCDp2092>TyfPY&C~g* ziE>%-ORGaaoh&K|U&aKXI}3o>bzSB}ML1*@mOCn8xZb@EHfYHt!z}0&a8^cSuBN-S8uJn-dXx0BeQAS89MyFe(GG00L?HK5&DF$ghiUgm(5?&xkJPgXl4kT=f$+TGxPqU>_4QA_`e**t zzjXbPf9iR?(vLmNU{8CPs0L-S3ByIDO}3hZquAUjt;?{a(Czl;Sy?%a~e^ojVy|dZmHLX)QHp>gR-O(F9 z2?K-eH_6v$8YmbR)0^YZy$2L`8Bmc5gzb#$>cmtgz3Rcaa9Cd2UOMJUXjB?kL#6wR z9I_gMFAh2}mJ?_@b@KEw6& z^YODUzxT`k%F8>m_{7vav#-GJ?BopV6uBqy0HLH)KXQr{s0GbZVr!T$Qxw_KJ+3oJ z=-?C+DWf7ppT{-G>BN`1;CcsKX74Jmj%^WQVeT}cDGSs!@Bbuir3ZLLE5#Z!i1#+x zvn36Ep>%L|SfjX5m&B7Qg|*^Eb^3-hKn=-Hy#wi98t-dfC2!w(I|qzukqvXy$XXfJa=4X};cEx(L5NMrQID{*`i(&E!k_hYMgQ{D#=B4h`A62JCr%7v;uK9eI zsJ$-432F=$DYO+R(br0Q_$|J(gqhp-e)azztnW#oBTPaU_h`}T=^3{7dc69kpZz_IF z2)&jWyFC_>&hWE5hD8*UawPYM5M|%S+ig0i9}l zb1@CiU5i36)NVBG^EfOW0t;E(poNaQHnW#w7pq6odfsZmjL~I7Dr+fCy)nzBTsOs= z4Onu@@lM25s>?y%XGw3{?`9$-=FlGI(gmIR*sUXKq#i`%Lk1c)l7JHtp3iG7`uF*^<|th`p$6eQyZIINQk66@cHYf$9F&Z^wZya{q*gVJm);lkpWFIDaS;m z6dVO%498(JLmU=Yp+#$qXk}7O4Q=YF(wj=n<{N5XKDl(3sVkrD@0?toifhXE(<^Y> zN9~loq8e+r9p8fC=FA7oJpsryvY~7z(&N4gBmyy;Y}Z?6%A|hF|!l+NsvyVqH;Lw2;s^m$QC8+;zduTO%7A#OVW}R z^-WN)Hl;d5*3O9msg^`AqmHhsbKNBVFe-Oqnsrs`CWD+ES6?G|@80bj0P}vjXU13V zi0v8MNlZq*nW%Iw*BmMBHe#)_rf*a)#*NBbV9+Y}n?*SzIZ15HH_R6^x}NxH^fW7SSY%ZC{_@=88jWg%ct@e2;*C^@5*% z^X~cg|GUo~pBapg7sB9AvjEGqYT2K2j5{s@?R2J&@3c3alpMgM$*O_|#x&>!KGDH? zCg&aG+1l;&pQNzLcgPu**y+f6gAv@KEyvk);d;HJ)q?5hfvbPnG1slqN(dIO6q=Q* zmRM5CewlijeG&=LkdNZ@f=wil0ZJh~bvM(tnj2Mjb9@$uvE+ef$Q<2r7kUNx9Vdvu zEpZn|wH!s*b2Hdkxks5F>$qiboNi-4?jKqvLYe09iUwd!5eKMDW$gEzrK{fQO~5;TgVjY^OP7pMvj9;c zi6oOXVFfMpP=;=O(qo46gJ1c4`d%Fy+ZMo8vtfB8>S#OoK+l;Tjw@gaz3`6RT`BZP zDw>z+E+PdI?Pa$&IYvlg@a@6Z9_EMFHQ3lZtESW>t`4uQ%j!1Ft;*DrhQZc`6=4G( z+ZJm1)VH6+(nom7GmszDiA!@Xj5wi2pjo>{Y2~ZFOB%X}J>;bFI%=e>aQK=J$$HS; zfx3N7$cv}>qYyZ^1mkpQST_z>MBJG^+~nCajE$9(Cb!UhVEK>_Cf3Nz9%o|Bko}2< zk9cRd)rsf0#S@tZ?vJFu@l>lAFAuYK;V-Z6^136@GPB`MDLE#KxVmB$Acn zb4>9-Qg@S<3hi9j(DQtqpR#|YB{QVqcLk(|Sxkvoa{@Qoadjo57N!eEUat6^l-~NJ zS-{P2v&9IkPhRUhHFfG6F(IF1_P zb|_l?MiX26V$|q?^xFCS&@*>|o^kga6K?h(yy6zm@rOTfkC0EB34w$Xc!f4MJPDr7 zW_R~Pja+k=P0{lttEEs2&g>}=*Yf`vaE==iDV6!k=>%19M$1g8A&VCa9ylsj)nj~S z7G@IiuGFL_71xC}%*BTAqF8|rSALXg*@-OewUX$(^+`2t7lS1h>i3^!ZR zhMjA(Mo+6Gme?NEX)ql@_rYq_&y%)meGbYV|HrTRBf#m$EpSE9{jut$kN}|jUO22%cpjCmB0+d^`*9oJ z7vTjr?O4m!ajcO-*`B5tvv!O`j!)aa#$mB5JXVx(KNGf1uS}W^0dPsLb7SymaD0j8 z(pVA;i^pm7&)%X$$F#X++Y~#TnS*RLzl21lArQ2KB9k;7cizE7hAN6hY}DMsB=NIK z`17-4>!#q37%pkSP~EKaJFNa$!b9$EW@?+at=?A(SSqW$>m2Xn;=~=dakO5xOZ?j9 zX4lb&%t+Lhgt#)LcDmP^31X65wmg1oC9*P=Pq^QWO0x{IX$iEhc62Y7udMF zCzh;J)^d>_Dde}U4*d>BR0f#C!1IG&{oW`IDmYu)ZQhBdD7VzcaqFUcPM=l=w>3X3 z#?^9JUCXj8JkEK7~z4h2EN^3kcGKtfp#4%{EBlZ zB=6jbHsbrOg{!_mpAKRk_fu{=s2f+WlOHuU))CXOT$EnGWLpOwEE9jpDxv}uyTU^s zu=*Lj45jzhbxVOMhNwLKPTihMJ5B2N6XxX{(D_IvqFOk4-1o_R!%FBFRz!Bb%A(Ln z_D~YYH9?!w|CF_(?BbCP9vl$odcJ)8=K1aKzJLGI=kqf6^LRY6(Pa)Ys;~i#7bEvEI>HtMCi|D3a< z=QNn(b*q48D-0xHWR&Ek*u|75x;E>wE0<^4iXLW_Dq}VYS=9J08+P8phfJdinn%{E z3r85n2zqf|Jv9YaCFqt1*0c9kw ziYRVffc3xD*~^W8Ix1i* zv}1+&U>lqlG}oHMQ8&|hFUMW7nf!xJTO~Ye5{SghXAt(XE&J$`8Y6J`Xm{aBZ0=dx(J2+SkI)WL~sDu0%r zGd)>Eqg9ys5o4Ujibdtk<+t1d!o=L%1#>M?Aw~s@3TB(T&fF`sezC`?49k*^+^BO& zWp8@+;JZYCI!$ub3R7HF*sO3a;Z>6;m4UZX!7YFwTWr|6BP=-zWC_N#3dpwdbxQeB zu8(Y=13b%sWosdbVP~nV-w59wy!>mFp={@6t6g~yYAF$Jcm90z;ireIE2PG;(6j9K zQUG(>EM0~+|FN^2N$7*xXBGxKKltS@n+ZCB z<68jS;DL%0Db+6e!A&u_1oWj|M9ykE`K~8 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

7}K=#gns} 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

pL$#7+&PeG^_VMS!G>?AiwO`@7<;3E1 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

W)~*?vNyq>rS8>B_aBPL!6yHTN=klMxYq+q(p6r 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%0ADH?nC;4o&!1pWC+U1$fYw{OoJ=y1n? zJeNc4vrjILlJYKGv(h6SLDz#uZ@WC}s2lwEcWygvjQ>)fum#u#N>o&Vh1KxXDJ zUsxWH2`m_)yTy=e3uIWCRL8sH-NaP-wW?5)j~Oyany%+l#GSJZ z${t3yjtZH5y2ttYw-|DyLbTN}_SE9)iKSL2ry9b+Dqv;z`!+~^nh4TASNm^ z(y6UPmU>Mt5Wf`8je!K5pFN+zL=!%#RV zZP)h1Jdzhp6ldoxT}qj1N^eweBM8R@FD$0~7`8@LME5detB2+qhq^rM0n)ITysC)B zK50^7fOp^+qK5IS2|>vUt2E1*kv9T6qMfDOYI@BkMO5a ziODuQ*g*y|Pq;B`u*4ckRpWA=&LQEvXv&;0n3< z`AFeC##PeHXYTmi{kA|pS@`&f^^KtSo;y2%^@PswQ^6bdv^W@Y2yI;}#5}$Y-0_>v z1GG_2h1XfO8z|UKhNq9#H*BJy`-`ht?7zux-~QcSKmQiL`td>3&$NYP>Mu^tBbA4> z!kT_`X9OLLMBQ_>wN_T(-P;@1uDum#N3X*BiHIfBb#$kfm{Ew`ZN1)_f#bwP|g z$yHCc1I;j|9V_Gcm|U)&3G{RJG71tE;y95@0=el1!>#XlDfdOTG%;8vc_|W8Dn3DI zG76x2O9UXIKDhd5on2*|@8qdw1|AGbWUP;i&!-AR|8LwWCt{(ida(B6X)gCRgVR)_ zrP3eHZa$4=Ay(>pn9xC_*Rth=Wd6_Y3BsvS;YPn39H(aInj9K39V#B(GLjmO>YFU? z!r8j>kbKY!v7CO@}r#E1y^k%WW_^*28r@u;=n^K^9GURTt5k9WVKuLXy2Dco?VDucoU_Y3sXZ z_t^L9Q6R_2~2#{mMmlJxjhlj5Wx$>z?uL zs8@?n;FwFmRHOW^B3F~=jM)+#HJ$kdT$J2+^1Bwre zw~e#qVxtR`9FOs;a71C_L{JS?G4#E>H|onQ@j-#(SnlI8%j9Y&$wa2+35EZkC9TGr zKiJVe6C%zrR^*Dc0{nkTUtiIPXG%~y_ae zy|>6SdOQc&%4vN%&TP??&g4bvP+= z_t+Dls!IkDn}ENlh~7JTRTTg{xpJK`TgL}hU?FN{R-vmnvqlGUCBIIH`%EMI>;)sh z28gs-F8biJHNu%4ywB0~PqO0q3?|RYQy|<2j z)u@Rj5<#Pr0;oj{wkv`vbVVa+EUE_O7apr+@X^9}yjI zu~gODo>%mFCp#V>;#q;!UH!gxb5(4vbsq3`WGHs>=rI*W!@TTwD`9UgT0Mlz1T2V( zJ{$s zi084ErlkF2gglaiI1InN)jOG7i8acE*xwWXt&_d=NNvu(#$ylNC zrj8Kk1E4Z9c60Eu>82hdLMV`=>sk3QoT$1x^Lg?C7A4Yf3_GZ!D?KCKor$$_VI?vb_O7?8 zwas3;2qa;nU8XH#SN6(Z*2l;G!$0}c{p~}+1G#r5HF#hOpUmn`D)x+NZ2bF(IM64T zR}(s)PJ4IlcsE!0&`2d!L`HY*m{oK0T4`)b0n-j%$05SQuhHaR(jq+u)3A+id0jXz zu2?xJSoTezPlkEaM2~P9WS;!2f&0p~>7JvnWvg@C7cn$2L=cWESW3p2H%M?nN{{D9 z!i5D+8xsg`T#-x1Da+iof_Vc|*1?!uVZ$J5V8Z88oeXUW9B(?oai7wo+Q46v+Fxz7f#`hi3sNS7qj*4 zaBR=D8ttojIBIyta{4SDpAdu1Pl`;Sk(+lU?Cm>lPwcapiS(G z+^(XH>->z!x&Zr-Mo?;2yDb>I#XgqzZ(j0^@ZHy`K)Xp6tlhM=5~d&*fC)GjHZql0 z=2~ejuh(#S>pJgmRqyJ$V*IKp$5H$IEZy4F6YHK?Geqx->UY=PvB*B+9u8p#%J7d@GsKCm#fVpjXw6#f%ARB>DI zNJ~})Iu~+*qU(i+z^}{L*u7`h+SPlQM;K*!0t?xzjXp56lG=apX?-t$=?r!b5yc49RFfT9FnW2*3;gx?3B%Y}oLKZ9*3mhEs# z>Y$IMBTDZNDNtam*|QG}4D!^evm@kFKE*&Uq~nSvOSu}-W(F{CJ|aM$i+RnUS0V|RUFoibqW zLpn)sEc>gocSgJ#ofNti)}!&CgKga(s#k2M(x0I93y_d$2V$!jic40|ecUV<9_;w; z78i^59*?MA3EP*+(?Tn(P)5`gnnswVS%nNP#-yGtXqCR@4W=YEPSlgg95mZV2rxC7 zFwHVVvFxkvgKDfJAUqy3ekNs5a0O@H49Ckf3`*8lw%@6SK} z$w$?qB)ho|gQ9KrZe$~S6E(m1xm7h{tz3K4VA2WHu8t;%e!rw)fi%aJ4-%;u;krDT zGdIOVELLs#Seo!FEi9Xo=q|9Nthe7!QdSZD$QF;^t>3Jl|LVW{?faYe7Yal(KQ+l7 zo@c3VY0j!Fh;6&aH9nZ?Io!9*Ql%C2)l_-hqh*P*oOe>Aj&!aDM^vgRa^h74O0BYB zY_=%wg%dscNzSMFdv!mo5k$$&iHtY^Hg__RxrBf-YI8yZ%P;Xef@Hm z42?#9$QU-hzP{csI>ch>w4cJX+N&gq9Y<=Rw&H)J4ePEGQVH7i4%H|Oy3M$h3g8t zo_JgpX8h1554ocQyW{gU6EWmAt*sok%16Ch(-jik6C zwck~Rre$OD@+Oj2n5$qEdw6R{aUhshXcCB1rjgoUrlHDkzA`=CnXy)?9h<~q>b{yexe}Zhy?p#|mK7ah}_w_@fK5?AdHO~Qb3%Wh49zvPQ z#Gzj{qQ;~&H@zE@5f~qTsigFb;_r0pHOj?GqM8yKeFDYSGOcP&Rel^pL?si zn`}}NX?Y+5hCLMo+kl52+JDJ_|D5LrJTqXxGg}ZunJ`JSKdS2NT;airSed6ONCKs* zy7!#--8*xAL_D!tD#OwGbH>jS<$L>zzU<@6o9_jZe1p$2)opp_*+={vb8t*xPYw=c zGA9Qz!Bi+bPFt=xbzn9U)j_jh?ZdF)co?ih4oSNY!=^v}%viM5!FXiZ+C%yip<;XQ zG}KTc*wamgIn@`+$5Ev8Qm*@RWxT2b|1-j)|n6|PI&!FX8h?J7M_a_XpI-`5EqrSoqKJF zwRwh}w=r&_nAN(&NF76dVMwR)vAI;9|C2L}%n7&kk5O@UNp{)E7pncNE{eq5ozZi# zT`j&~UTDyW$o#o}_&@*Q-9P^2!+K&H6%#A5BAFWAa91<)jenvvJN;cF+o#h1`7Q-w zc4SiaVn;pH?)hNo5jRdi$pSs4=_J=CA}GwgH^JT*gpN%lkWa4Ecn8RgUBA!Y{?+@B z|JASm>DTdl=7YqlPnaByghk8h=-4u0RnY`meuHf$L zyO)frGz8gOoohKHrr*cOD!iNzak8~@Y)B`&c=BD_C8TS`51(Wmtv$D%WfXTg7=>Fq zH~Ar8fY0`=S1?;ck!8b&DCZI864XC_OtzA#yk4?C8u-?eHSz}PSS+bP{= zW`z-7Fp1M6*xlQY$DO88hO6nMZTiXO)R1+>J+?Xlcg5R;$pn<_VgX_v2Sub2l633d zmGKm%Z+T2<=S~@@1|Gf7sCCkO;ad_l`}_p(m=Iyh)qFbLL-v2WaMa7jX=|yVSrs5Z zk)JmK)hr{^_0doR+(>iQ^=fN(&GWvm@gTHNd{#r&q4JhcI^(A%1I|O$GkUHw1LgHv z9}n<|n94rWiruWQYa^<@>-?tI6*e;2IIGL@xxM0KkGFbmUG}2lNf$p`YeKnk>9uMZ z9Ww6U1pah-EAuEEK2vd*H;1@(Gbtk@A02?c7#%YzrGr)FFn$>~(f6>#RL|F;D`6d) zsY7SSWiCYw6bpM`)UpOBBlD1%hE2!3qJ!KCPptxTJ)2G(5l@b1;tLz^ro`h?V$Voa z49ztjG#uhR?M$62{p#uyUuuAUJW{4huXe^EAKXgrMd5V(v5vP~h=;fmJ$vrIkjOLu zY{+5SzIba=F3%K4KgT0AdXQKKkpuPdF!8{vYO*rUj%tFkB#_Yu507dPmrDNtwBmRe z;cjBS7TID`VlkP;VX+hgk;PV3GO%0s0!JQ20BeC>$~K9b6@9fK1FM6GQewS!yEs>A zw<}R?FKJkk7vIoWB*pk-wl9y|fy4u5Lsd#NutqsMU^tuEphOw?cS=*hVn~84$}J#2 zn8xeo8Z?#~wF8-o4ykx+zk63Hmb0b&W#}~{p%$jaEBf;{&Gbf9)pSf}MO~pVlAe|} z@eAbM$t>mbAn>sG{ny_m1bW?d1_5=X$ssG zcd%1`bOXb{$(9Vq!ysGtuCs4@yfr}V4t8FPL2xVKLkj_m`8|Xise*eiDiSe!4&rmR zW`{GSzgO#BZ8uu^bk2wtr6_il)UFY1BkQDsk(Q99hMXPF&QHc?ME>LNe))&@+xqxX z!!xYag302Js@+*f%tQeDvj)G?H~(83tMlyld=35l<;wLup1^iz9-`l;eq426{X8=I z?`F5VW!o8xWJ9Z$7{r_GELAK#UWYeWXaoBXKmPgO{Q8&w?D@=}fA{9cX9Tp#I5%Wq zkaExkF9@n8m zX%XEBpkgbrRYit`c2p&xW4g8C<~%u31L+{#?!lUyH<)uyWSs`4?AhhA>|E&4N#;3{ zRKB0O%Vx7Y40Zt}PBtF6NPo7|;2|Shgv^f5j_GzxqlhsfPvVJe}cf&ts0n9npYZX7|;S=xD#e0x`xY8)u1Wlj1NJK|7Zu_-GLty{!Cfu6qnte@u4Mg zBJRw^@Hkhl+>t4|(PbAyS6RuNWH?;mPbC3DUK;ZD ztcKy<4sjsrScNhEl#H1v!xF`G1Wea39mZA>9XMAPu3y~KD|@n!>HEoZpK>3M$Nj} ztO`~xAX2C>axyjogcg?Czapxw?PhU_ebU$7T1(p`P$$`1)PRSpXW_d1pE!=F70Bk_ zck|V)WU&BePii(kh=I8b_4-aZS?-L;IhU?PkF?Mh^;7& zE+r>yT0Xh{njM%iY-H{A8}}0j1D*{TFSfBTs^vz*f}5!oLaR&fT}}fwyUZ- zi=t!Xbh9>SJy|<5-@VCLYbpIZ(^0Kb(kv{p3Q5+=M+!VvY!YinWYxFW{}ey|kKg}~ z|NdXS&(Hr@@%@p*Hamveez{~M);ecMFT_@&rmt!+exbi*4N2+%$9n4? zK>dOe`5bjix2BDM)-YqA6+}vV$=;}VFRMOZtzjp8c-VHa81xEPLNppEO(3K#zy7te z9gAf-rS^w)0$_`Bbcb7}WwGzck8w|Ow%&arm1wI1t``&pGJE$PpF#Im8#7o@be6T# z2-@LwD%S}|jiDLKH`yZgKE~V3xm=XvT7QlSe1gDM>M}|Qgw^-ddcN$F!+MZ+%}W7f((3^lWd7V?ct>8tK8!zq_6`ylE67w`UQNnAs2YR zCtZwY-2o9>$l?<+VdDQW)$41;6^5h_&D4grF%!V!$msr*8in0#&cl9BxlfC80<*T@ zGIYqrD1Gw5974@>bjlQDYad+RpwM>*o;6%7gFlXnqBzP_P`XNq1 zH@imUWB|dGZ>z^KaoIf?_@DmkU-xKUavUaBI^qe@Uw6HVH(=TT$Q7k`$QlPSkBr}s4Tq~sv z4{|k%MeOgKK~>~>){3Yow1h7sHVTfPqCxqYOlE?+2mJt&t$({rE_pD1JvYiR%Zr?- zTn!}~^@f_7L5aFvQ2>&_;*Y=m0Z&7Hh>0-gHREq5GIA7QIHT^K{vJv2kqwMyOsNv- zp|zvoa$qXmjHPZ(AqEXD?h}7STvR9_C5N-BMV#9taW8D_jBz44ds9=cRAAj$$nJ-G zbaz1TAvVY_={x-k<`iwCPj4k&9}&cQoUoh6mSYS07Zm>ZS34!Ow?IcfaP!tZez8K| zu|}9P0vRfOkj$l;VD_|{Uk1tvoZ%m@!l!UL!^uyT4w{THe)nhc%nt3sW$~K8O`76K zhn{Y?TlhQo?>=)=bAJtcp!t3}BYt#2L_Epr?9;6tRX_dGf7{M#72J~YI2mkY3!3zE z*+aJoRb2sl-Eliq;r9Cgoh2HSRF5q@uCexeW0Q`TFrs$vmN~>(67vdd3@)-`YKs zFA{V-~4g**7Z{&?7jVz)kI^y4-vjLd2tSma7n6Rn2VWDW~N;#hm{Cp+j9D^uxr zdLuzH7aFY}QMrKUql4|Aj3?@kzwH0vU%&tMFC%{W2Ymc66WgfmCpmO6ufsjL=gPgL z))l=xQW=7A1+j2B#fI12LprVwLCE;Rkhc46wY9S641O5$PR^mZcQ~t8ZuU9ebDVBS zkT%;~sk{RQui5QbG~SVm^Uhi?Y=wh|yg0?vQO4F5`epR1#~YEztT9pcVJ8=WaX5-WMC2p|#FdOH(XYRBwP$q!=b+Xt z9sAd%!PyzkVf27=g9K3GS^3O$Eav zdJIZT(diUr%e5xTb%N!S=Tn+t0Vh}?VwZ@F#2~E({w+LGTR-M>T0)zZ@gOU>v%H5CZ?#a%+j?q*j@b(JQ*Jt=jB4ueWYzf;ZPE< zIr93GFyUpcM{0b~v@IX+j!?SgU~WNMf}8G1<_A$6V9&;>9l}H+X$;V3+_mJWUa5)H z4Ynh_CkHe5zkC1<3_p^p@R@mWQqvf`IY3T$__(E0*q1KY%7eTic%2NBV}HYF%j5d8 zZ~}z<&hDd|B!F6CKNp)=Lbt8MUv}@v`nSt6nkJ`+^fUSvpOUC0CdRMO~%H^~X z4bWqiS*`k+hz$)BUqTVb3G6`P)vH~#bLADsrlqyKwp9?Wo4~{FXakk+uYdbJKNx%W zHp_vKJ&#!u_wLZpG)f~eEQ@a=V#VU&YHGM}p&z>Aeia@h0`0kTdS|oTI~ewGg+K}r z0gt1)e_q}H8~rtaT=>-$1JX`_9v;(Pf+`H_l{Nw@fuiw=DRc^kSZI@jiflD~JU_FQWQ9(HQ0Nq@03kV6BtR0-xhuIF`RGTs&BX?vlx#I-$eFxh2|0TcOozF)|iE;*t56CF*w*v zesP!wqU9xemx>j@3T`GppWpoZ-~CkivGyOycSrU)4Us=;A*-sQiaT?mn7KMzP9`&O z?@HuatBTDgkSIIVrm%JZna|P!n?!u{Uy(Tvz}`C_1mm;b&04;zD>6y~q9EV56JBHj zCh)QlrDU(oy`I1R>Hqy#zy0%g|50n1Qe)F;bw2A!0A(~w7en2!tGIMts-{dCXB^;f zKjSFPR>pp{IzFKCxP+jS4Nh+2;{nZdwI}5XFv>j zRjdJFULY}hxZw5U^dv-NDyUe~Y&U!TH)@Nkb?zzm-^Ok&vA%5VLY1aeUqWkB=1#N{ zzogXY=f8a1GY%P;cciTDedEfCHoIxma&?(v8O<`c3!lI%Out z@YkV;8;f#V_(ukcWV{=5aXzyZ^55R4Q(Zy6=ZrJ|DKn}#gQtE|oIH9u0zXma)%z`Q5{JmyWzvp2_xbE=TOfWkgg-kPY+3b9pWVdG^5I>2kyS zPcbs{h^PmaVK31|j5k-TzKV7AeK~K*R_UBf7UDA)t_UqQTQ?A?fgDJ$-GUq?D zM(bI^39pfI^bDt@sPx!HQMhDu>K<{TF$QY0dlVEV}YfLKDdj%`hP_@r9c1*wsOCTe>yky}{fo)-4Hr{~2 zQpO19Ph~N<1~~^?bAwe4vn*|eCn&N1HYIirq*Y=xsp7XR#2tI1Xm_Pz$P@u~$K4?U z@ID`6YegqcJO4V0o0<=4j8uK4%TF3v+QKvY09V6SzTcY&vunR>b@-;Xk!^@uYptcx z1GtrQ96+ODNJJ|9Hpq$v09A4Vb6-ov0Fj&iQY~#&ba$(0k;TQ>j=A37fBo_NAhaW9 z9tm81bi;f4x2c0up4ioGkGt~O8S#Eeav8*EKamP`@I$YM$(a0RB^_24XEAJ@iPL%} zA2f>`SD_>=zy45y*S|9QqZ`b%fz-_nT{U*Q}b z_E;x}T`$NolZhBzpE*pPKgsCTUj$j~kovp;Fy6F}6B0dXpJ0IT6vc!v^(6)1(f5R0taa1*%`;2vmI+>I2?w`c5AD` zgWJAMISM(8Hj5YJBO{-qeK1O1vFfp9nM*fd2SVnMC%t0p40~#`H%_Web`;|MIU|4f z{_wZIe9vz`uJ@-tfl`}uG$EgsS3c__orn--`d7WdSecCy>|jZ=El{0XXK=O#jq;nZPj-fDz0`m$;&%lZ=8 zwOqm$WAC~*F98YpwBQp%C&ckEfxm&+G53+S) zMXs=Dk5UJ0LC0)n#^5%1{mwLg=5*ukvsqtsNd&NPr|}G%dN}GdK5!7hs_EjO5Jchy zH&3<=E%FDx^f@8usn~u~;dHHBo@k6cxaSKw8pRko(GfgnMkWhX1fwD9#S>{W9-JDT z{E|h#vl5Gn5>SnKr<8f7k^Yu5ujk8NiNnw9S1evgCYA zP6Rk`W6p--OM#)dIm`D>laQ?>oJLwl`Mz=)jEG|&R!ai^j&`@b$1#aYl&!m;s6!#@dS;8Ost3}TE>BL4k~(;NiG4;$|D`#^S(r?@ zdkPgVx;sgvcn1}pB(ULNRXLd%9Jj)OfJ^}mPULU;Oxtx%k~<@s*AgOdZV=WNcTCqm zxX2W>2V~li|D&78Vfm6>uICIX`b$|S(cp9kc@`F<_+uOoNH_s|PQPVCIc8$tJ)+Yb zb9wQf2ie)@a~bSd*BLvA7JZ?~yhglV@Y;}8E2VDu(Swxnqzt}<6 zX040Sp9j=jwRh{s676(L)c8zX9-Sy48dEsp>2V1oGdmrs&ytn79-H7|2Lr{FL9cvI zFf$*O_l$^-^&l$1V%2_w^4E0-t~2fF!>mfCtV%Mry5oT#jo&rb!_I_cRPl!&*Y_X4 zMUMoz^iEbfpjuLE^(PbKuBWyf)iBpg9t~9$_CCYxvoSj#;(=A;Z>s7kriu@{rshFW?nvf}rxy2T^ zun@csGmTus1q%ih^Gsz8lY${ zJ*xy653z!uSkHg>-B17F{myUeqyB))`#u&CWUR=QY4NYMCcuL7sanfS2Kg2$<>o+r zmi#OMtazR$s;XjxMebcy?CmmQpwc4eEua(M|yo^066&&qcocwc3MJ{Q{j4c`vt?#kf zxt;v#keCRPpA|*En<^8D&YcIYwS2G8TA{ZcfO#Tqbo*u=ru5a19q6kLsL5Z6ldv)h zj?E)@8NacGN}NQ>L!0*j(&6^~yFbPSa|%0Lp&+j}x}FU+I12`)g@8C5nI)Vj+?>MQ zaGl4q$V-{5jMK3X%&Luae7B&i6Zypn9J*NXqCXf21XluwolDduy}qD;4$+M$PuMjv z^bar;+~ykvF0j8QTu%H>4KatN6(Md2GqJ5vza%eF{9TYAkS7- ztzDdaqq>^LGwS)80b6wh2-7xK1RgnBJ*JHdFns{;o9CS+k8!|AYR)ySOWOnJ2|nn$ zq~~j0&f(M~Gp|#+b-S~-u;CXq*Aei+aTCOD-9bjW_`wNzzi9b0vz|yajvrN{iaoId ziUDO+pzwhc5wh^7Sg%--8a;G;rt_cjGOOe4b%VzcI~g(@PLd|DS6G^3oWuEotnpUH zO%on_a7=&=B3G;@YQ?_A%yT+@YP<2d+@CkT&M2m3ZH#YnFoWz9&N7Lm@4~%l+fvgc# zQA*S4C&F^_$uri9Z2twlB=Fj)!(|i#F#h4TAG?qyh3Pu7l=64IdPd3n-0q_9DwZ=d zn4}g`C8c?TQ@Rz{`kUUDwe?HeiH+Txp4e!<kHvy4^uI*GjPmfI;jX>tnI1{oWaw znV6$W)T4-Qc??*GUtqFmwV*jTNSE^ZCRG6NHrGMw4mF7pRWPj2u?%f$^k*)N80xPiP>WQTWRc}bq z0Ts-r2QwbD!?B!_;&kQ;I~RBL$Y3+7qf@nEC0r+k>rl8yir*ZhMfaDA~E} zkN+bx8xtaVg8f(8YFv0p7N@;w(Flk&>A@%3LEr?=dc72I($$*8B|j!P|C=NgR=BgzGBirqAK{>lx5Pu-g3T|N2kolkC;R7U{1jk2R9o_NcL>1?*(FU91h|byA-H0#(RD^tPe7T^i58GgCSyGz;*mdT46CPt+fj{rN@GJv@nb?-fr^hG{^Rd{ z`up{pjQo+Gk*gB#ehKE2>siHGYnA?_674dw>J0?BafU?@K^kkw0J#=2%S@4asm;sY zUc6-@OY)c;gbAGHGW}OExVzhpuCS58U3?40)fnkO6?Q!F^Zsvt^V1JM{eAs{U}iG* zMl8k`%ZNw{^o7SQcWHsCRj0Q`!7qGZEe3(E+QS8OiwjdO z5(cM@y<&I*MEDni=g9hV{v^`~msC|qCxcJQQ-Cl8D>d8%cYyIw&zhDwb%H%RTbRj{ zo?>aSoNpb7knyCf`xFKJMEhn}pIb$6=YF?_ur#c!5{dnIkHtQn49{_L@{269-CLPB zgCX-yk5=}eRFoV;u-;*pe32*7&t7kImU@p_w(;_jE5;e?^icy*>$)$Vxkp@~GIsUU z#pFfbR@3n0k`zcmLA)$om#Eml3(4U7tLZ@9#t`7n>8-ALzT_8)CdPwF?RyX!^6FA( zo9KEx^2Q7`sNwSG6^pel$f$h{5wS$U@w{Vw`CZ}yR`s?m=83YfnPxS0v}C-)?z7I> zcmgusiWLGcX@hqZ{h08cUN7&h;RwCte0g%jP9AC(%qz|U)oX7KRA**?W6>JYWLTue zvz3u@aOrg*4!9xi7qLcKckonl=k?BR&)eG7>H0|GT7Jqv=OGK8h;eRN3bYNc$}Uve z&_03==vd_PAb{~smF0H2HSn$c<1w6%HjtfQBn7}a5!R{q({Ga`%?g3-3H!ungi1=Q z(LKLg4(~+--`Uyp8bDb7H@{Eihy7(M@<3b@Nw zj83vNupZaCp`T=BtRy`#Ib#|^4#>$ns*2+w>kZkBq3%J|R)?*va9n>Y`rbe+qtn3M-ZO_$@ zO1r{3Pxxc4XZ5D*!4*Z-sjWb6IVQ>4+6ZQ>rA#@Ems@Tj;v7Y}NK}NUwMp*&2`gaY zTb2WqtKv(rp+K(g^G5}OARGd_{97q_)YS`1q1@@t{N{i?oFib9sd{%T#=Osgx_LwJP z1OXx010}A*$a*HTbmq8PO_W0vCgjOc)%1Svq+VYM&$4oav_=571wS#%{N;4zGU=^L zd0_98;Y1%LRf_7-^e)@KL^InvO8rusW*jkzJ^95?#((<5{@ur4N99w$6f3YZxYk37 zJX;wNJ)jJts^0y%^6414cB_=~68aP`X?`<;T!OS2f7(i)mATS`q+)T`7MLQ@lZ+7e zx<_qnf*XmK+*M1nTc(XZ{OG?vYkz)U_?y3apZZ;{UXJA@VJDIK?zSw#r!xY7N%7~@qx&2K_qwV*t#RA-nLYae@h_?q$d{TA}dj zpPNF|v0LlH0nXlDeQ9*--eU`GkkXV#Y2wTwov|{a-iXvN=(VJ%{9|j-a0zu*VoiWz zlX8`z>9K9;#y_^(e*bJO?n0t`*q+TTjJ%qti*Dp1-tlXt2^3%Sgk=ffRjdc;8MsqP}5()0SO(}u6(Y;O8(E=R7 zZPQ`5CPc@ZYJ#mZp$;E-aK3p?KOFC!?i}YJD=^Y9yIG3sB6}ye5PHG4s_@6!9yYeX1xPy`OwL=vMwr=nG)& zT~!fV+C1wgYR=#%rOWM|X)ZnKME3e#`-De%I06tQT~$L~?%w$fBT6w{BwK;bBEU@K z(=1bzV813Xu`&qXL9U(HdUTZ!+3ra;L99*DsfGL#L}Dqh!>D(7PQPDTg15lqt?q-M z7cZLRC3h=J$z8j-d5v-5+n#D0_cO4Wh1$P-|Mim{S=N?hJ??tnP$gxi3|DCf(Zy8Q z#a-8x>C>v+o&#e{X~0I*9J^vWMQk=uHPO456`SJh# z9KSptQ5Bz`nIFt=$cKAWZm|uY3XLD*`FM~p=!(s-?ARLCu3~2ydZE}PX=lRrrnWjX z72I#GC7ykE+=0y8RqyuGtatQbvL_cAYwy>XqOQ5Q8e;dXc;6qs|M(X_Vtu~xe2)n1 zpSe5L$v7#@>cDKCF)0naf(7jw%Y|bn6{ShAIIq9?_9KbJ$oMC(W#qEJA$87ZG=uvW z{jvIywudB;H^F|KfYCCn;A^XRo_W7kSior~$2m4aJdZ?h)#CP{Na->aVHvuERA77U zZoIJ`R|5x*i17)lD;BLowG_nu3}=FIsF<87lAQwmX=9yYt0W@OJWi?8P8%92N~>Ol z9RZc}UKe$S^1qpt80MMl)LjEHl9$_TEkp(P+wvT_2fLQqZhdc1O1sG7$^;wf@9Pj` z8xGk_L#>QMa zcp*8r7H%Ym3>XeI?Hapx9^#XONAHuW-)hcZ$cuDCD`#nZy1p>PyO(|+Lz0(x6g*W~ zzY(vdo-yH(+zN@^vyyGDG0_|N=Bst8YB_?dE?=u; z`i|&Gw#?I7V_Ru;Rb`>|$)!fyqdXLW9f8fURMO&bdr($GyT6#yf(AxWx*>SngCZ@}w zSrMhHI9-V~$^52dSG7L^B|?hMiavY@?g2_&3<7HSq6YvC<@Kj3y*v(p{aP=f8e%DP z%hY=@PcLGK7+NZOW%8Lu9?R1BK+H^Qw8y3l>}ZhvzP0fvm*px_fi87*m=%uR_`G ztVkjgWU^r4jdEDS*}}+7^q6V2RWCtuX`EMqLhT=Zvp#<13hNB}(=wHaXPqeE8h0hN zL~45{nS*QMy(OqAQoW;QD3xJ+H9Rj>U8abmR-LK;zJg4ll$Ufywm`8Qy1If^Gnue` zG_@$!F!(h!q@2Xqu?KnBtr80n>taU%rV+UKKpwz)I29o0;2yq6#d?%JA6p95?f{}9 zpIB4#`3PijJL}BMes6m1;ut$hOAuRGRF)7)6SWWdb3zLfr<3;;&J!eS6oR>Q7V9WE z8uz@s+(UZe;G<_i3rbe>^4a|D0gd#tG=0bo+_S1eg}y{W z7~Q7}M%Dh@#mfE5AAb9rjBlCmr?a{#`21*9;+k-K zKc|V12`-L48opY2hf@}P#mN&Nb3LnjkE5XFNEuxRe91IXdkLo4=Mn>3Mqif(cv2Z_ zuz$;wGq+p)a&&IwIMaFc25=%`=2>&4b*<9721ZCSDx{o+(0QJP207vCwvhGKckGKI zJeA#~u$s3wP8&0ls5`Iqdf@Xy|9K)~ap}F}xmG&wc=rvGSb25=tixICJwD6l^N!lF zs53za>vGnBnUcId17SVyF{!-J65c6YllGm6<=; z_!G<|v^9i#CUHD6VBPF>DmggsaNIY;xC)FHz<1N^pEFtx7w-WJ8>bVUIoE0+^QbgC zPUH$ot2<7we}+BE zxH?biq2Iwg|5ll8Y+KhblP|Lf?cXK}brG|sa7rf__2yHzH^GY@XZ4ge1Tc9!^GvVW zUlwsd19?rv&cV{pb8-f_oPXNtwxusUBT#s{b8s;6QEixc5k3th3HE7IxXiR}CM53d zzw0s4;L7})&vwTNc2)v64vkD?LGKpN&epp}@DRM0-Gig5!%dTl^WxmN_^_jRu9e=( zO-(Xz`}PL!;+c0a?*EzAC+ZpAF>*kuNE*$X=U0=rD-WEu|64)Y_W94_R@YyHT{*DA zG|)*CVB7|V#EBP@P;MfiW^C$MjjEHMTs_kAsBfa1E=CV~=`~Tq0-CiMwbG-DvtaG1W&xGif^vtJ5{P`(<6XEn{XyiS7v_nm{l2>r&0bDI zJ`ngc=xE@sG&__sb6`vi({U6(%!m-UCD{IY7;((nE&S^>5BCBvkgbJzhvr=r z&Cr;CWFD>2NsE+5Y*|gMC6vg-l%B?LuV*JT?wN}+_ZXz8h23&#dG z(c4qB-jy$$9m1^tTC3BQUpq+KO{%AQU(o_JS&Qv;m?&^Z8eXGNX#^N3+ek}7T4^Ox zX2#C6+;;#;MTN5F8pH1if4}OCrLm$}5!~B{cn%H}^x9!Lhz+0;1wFKm?ZvxI-aUO! zPt&dNkAzI5!fb4z!2nH6ntgI5a3M0*M;unWegY>1`MjGlg(seq8DEG{bED7+7VR?= z8THy{iRn5_wXOt!L!2I5vo$@FFx+*e2$tYstX!^p*&8%Gjh9s2EQ2x zS2471qGJnLQJa;J zL%8pR<;ihEp8S=oCmX-~^2=5Lea?h(W}QlX$fqLk$KU;z{}RB1BKFJL$d8ECKQXgc zSkVxUj&Ty!<&e5{%!yo@{(_Xuo*JcD^LFe4{p7C|>mp31!xG@=ltUW|y@qAo5H2+D zwAjnJ9o>(ybx*g{E##SRp`+pw`m^!pOOIWaO%^TZNgqUU{UU?dZC6XO?ZkMyIZr=h zV-&dIsEZ>8VK=U&N@C@mKjBE>(KGkhiykh~PUbc?V>YPtK2_D%o|^L2u&g(?@Rp#n zRXg0?LAbmkh(*=b5O!pH^F@H$`jj-wu}kN^u37-MM}A_lyf`j4PWRtgsa>98K*N~~a{(kpUAb8@hEzKED= zE(ApH=a=nP4=d9tbUhgD&vQ$?$vt46%k(=>;d~Tjv2N@y*U-#{?vAeAKvyCkhAQQ&k6s;}S;=IHE2OG5YYqM>r&>67_i!adCFnx~ zXC#CRpg#gbt&*n>8F!|Q*;>Zr)p7*-JaH^3Omz|5++`o*>TwPjWSpMpK zb>QI01u1Ypb~nj&@&YT^W%}ZSdZ~irzsLo8)w6cx4t&MeKcsivz4)X?94* z|Mp4Uo)EZ1z~xTt&$bjP!C-97cZa3Cmcq$qYu$k2OzHn=Yb)|A^AP5Ch7rmZ8fg#V ze_0E}Xl{9|<^IsDetiprM(AJmv8cg!WbtgSG@a7vfkx^jwFy7D6p|xr9oGU#tN~hL zuB#cDN+4Ho3yJ~ZPO)fgh6Go} zNt@O(7XdQa4(DPtti`}#j^?(PeM*buT2aBb=dXywDtr|4*Y)#%_b*<2+BH~m@U|`A z8TYNE?rwaY$F*P8MIrxT%eq9rBra7|+?!vMrH?~l#9bvP)`4o+$R3qkPxWSi$Rd8DJ%0-@U~h(8$%u|vp)w^(f!BJx&vh7DMPmED{6 z;FkM9W#SGdHxDj=SKWDR{|S5yD!-xR?1Y=9o6-<6o>(($#&FE!vxg+2Cy5-&h3A<4 zUyZ%C;_|BUV3ykEF%xZdj#I{>rTL%6tk=yX=GDwr(j1Q z;l|vt!F!$TK1GP0+a5{94h4qCwNgdUOzU=xHXT>zC z=N`M8-=^lXPo%UD(=N3M8Eye%MJK?WVMiZidELM4)c5u{Z*j94Y&kpp=i>~B4R9 z=15rI;bRDT`D~DntKGpvmU?|-|YuUJ{KpM6W!c423#k9 z+Fkero`@yb*mTi-H{}J8@BbeyKQk5tmFP{1XDb3tJF`HJ-SC|_^PoB&U|FO{5gAv; zlLrYYWp$;sh#JT25dg|TEB8&SERW-aXKbWb{Qv+U07*naR42VwXmYUa*nndtMDA5e zBemmPE3ul}l`^OITwx-wvz2BMNG3!+S((*Yku}DurUc{;uRF;XRrT2yyx7$MgSmVN zEks6@LOLS2E}{g0r<2QSLUUNV;Jo%i)qYup$Rq=s!NkgJ_3;ST*}p}0+=yskp-3;* ze&6D5WPvQG#4Bs%7J!6Lp4p7m@7gWr<)!I{mWo;p zLW_NB6x1V3NMTy6YDoh0H0xB7VtRQj0`0>Ckmh;fXTT`ULbS!QJ{CQMcpfd2djwJC z*q+Uv=Ezi8K#yp)j)O?;Wh6Ms*c)we$;Wf$)EhWWkx~|_7r}!CiV`{-!!|q#fngyT zJO&)hl9m<7`+ObxzMNj^qXVm zbK|}mdWkxijmbDQXHMgn6O_87_bw%1>7rG#->jyMaW3ae*8~Pi4}T<@84ZA~K2f#OJC%{Jg&Z$KQYZhxpa{{26>d z@x*8G-CcM!7r%GsD`GvGzDV#~+`BRZDtvm;ubE(gnb=iC?Du8uU7LKXt)UgGQ<2t_ zq7`=dUsEDb4H@hFBn>X##-eG@4J7dHH*-B7AAzi0tY0?PPh9`z|R|Q#G^NKo#Bfl1Depeg3Mcd&` zo03*V>diopRwnvzP#y#4Q(xrnf6^VpX@?_TZC?p`^RShWG1Gq(@9`fF|a_pMY)vHu*;J~NbUg(sT$dc+Pp zzr^bF-}q#Ma(l+wPF6<}!20~r!~4lzvSBNXv&-Q)8NXxGMcgh8c&EM3$D&lleY&+R z_nVf-8yNaj*fKDz$Nm_X^QgCV4J{%=4Q;W{6Vw;4Nc-B_Jqa1P-!E~sCut^UB7IhZ zC;7Yt;wneGbF}mk+evsw6YGYe-t%D4QN1xiP=2nkzJzsELTNS*zM5!835F@%N{i;a zDvW{ehy`=B){0=9@Uw;v_7snp?zK5J;dk(uy8$@y;CKy1eK% zo_6WZ{X+OpMk|%HPg=cm!b+i)rRU45qBg4xok|5`BjV(z^vZS@Ya_OE52_6~iKS_$ z`P*G3QGDiVF;-Q_KY&nX34`@4lm%j&yQ;bf>fbHB#%Xj^Wvqove5{qZ0PJQ*>q6iA zbGIT65&5KgRS$`=il&e4cgHPh6g;E-eM-!%Hn+9(Ex}|2wati#sO;<#@m+M^_lRM) z_yvo%GD9(jGbOFoa@V`tO3*I+K(2ncw>&+Wz$14q+7Sl=tmcU+sDJ=j`RhN!^Fzdb z?ZFs3E9Y85#;Ku(q1QE8mC*@QDsrx43#r1zBA0UBdq^}vK5=p%)U7#KEDShhD3mq6MR~XKX-rIw%kvk?@cFTEfk&g=nr%?96pazSK{kqV|L-rau>mHziVi?){ z^@?MZ9~F}%J}q>d9cJ)$cMS=SiZvq`5o@;y6xnX>5yU zNVX*_Fdp1Vk7jt}XbY)O5WUKflTjH_jM|cbd*mT74LjJQ>(}i0&V?~ldn~GY3Fmf* z)&u02^jZ0VMF9CiYf#;38S1FcMkeu!Y%wfatbin2&~BtMYC|+adZdc8yMxAmQb2IR zx{Buwn(z{fsQc0B_IPkg<5%w$->e8ub|v+|$-!Xtl-I>lAE2(jB^|4{1(2H{KYIgC zaCcABvL_v}@j-(8<)`P{-+kg;KYqjx#EWk_sZKhBb*PzfEw&|)40IS=3mGz(FqNH_ z*_?rs%-ph~7F8@dY;{*bX?~Rp%UYq{UTx+kN10p=;UY@j(p4VzK$1&Whi>erE`4n*62E%^v3O+-9yvrb{&~q$+n31&%AA(>+$>vqlZMICgmzC_&X5+G|9tw1DB-0}M1cBJQ zv9xC{fAG=atj!ArqL|Edt`FqAohaaW(oW1t9&+`{A=|TB;hJ`Hl9~?mS5x7jP25Io zB8~}YN_|E_-*Qo*q)Ked!{3_f@kF9r68Z#CU-QnOa9P`bFi#bD92^ozCb=7?h=HGp zI5@IaG_{mt&s0i$FZ>Soy2%Z;mMooJX#sgdXR7cdXUgo<0{=CspcAoXmN1W%(sZn2 zLyBN6KLox^{YT3At>C$kTFZe0vDJ>TdVx&Ip67WD*CqDN>IVb^n-vHdc11yK9)bR% z7Rs4O_Js=7Gdjz(P*_YIypGo;?jSxP+t9EHO#Z8 z?7gfwp}9pSK(Ird=<4|LUBxgx&WZwH3C$;qw*a=fQqPsc-DFbeFVJFW1MDh>fM74DrT;Sx{4%Hx7 z+naI+@g>g^8=f%_rgD{30&U^@_#WRr+$vuQ2QzA&3Db;v*j+A3xb=fRh&&0OkvSn)-zVaw$XUu4i*zVY%3o0YX};skY4jCTb*5NRDi zTUW)7;Lj?eC;lNKwxb#L%l$@-+uH`GgEWA_n9c6HHmf+Lm;+#VJrZswX6{EGM8Cci zIuc`KQ7-!?5piHGSnSMX9quhuw#_=(QkB5=n%9EZlir+HAjektN;fQt!vs5uI||(t z=3ZcyTF|7E4r`sKyHUWi+=z^L`U=sHt*X6rVsjvNLjsqASkbQ|;5mhDAIncP4k#n) zvp~kvwelZ#{QG~Z{NY>1FYDQrk1)<;_nMpaWHu&Y@9MGRdR8lV`b>NG+l8oBziL&M zuUf9>Bat7^lPxJpw1I5zU3)VK)T3n)D?P#5#rljRFfo@b%L*U^{%oYzX8NL*IfqLuXw-$dfvI_)$jH(R?c3d0ryjH?5Gmpt5B^`RL{X!&FNu48ilO}9P!nPU+4@Z%GopJ;n7K5x{M*rD@w#6 znd3a_V|?k(96M|%l|!*E6NPf4tyMYEsJyeG3NOi_j;_;ChI);gS7gE}jnTH)=+Y(W zoB^7)pw+$UICajSUKzo?{it<%S8$XlXonq-kkcuV9(SHhs+U)WRvWy*nvb`xF&}OE zxty`M{G=_(at%Qe$AB=gaMY$tB5EJ?e#kLuXN|NUcO#QUWdG!Rh8V`avv75;)(H!- zI!0I0jWFzA^L2(|WAMgRsF4^>D;0TEwe1S)3q{S(#z3UCam`rIXp&OfKf8I+_5`sl z?Cva28{>ldTr6?Yc51%`GfCoChkeh5EqeE_FhfD70Zl)tk2fy|FR$2S#Y}~1sTQ%H zRe@;peG9yuHNx`&>RGj*vSQwJ-Ot2fTxV=+ z=*GS~7L2K#&P*x~nqo zsTXCDD2x|E8co1u?L8K!MY!7(V>Lm4Zn z*yNrQ!Kp&w(7!O|RQIGqbP&~{NwjV0x<8TwT0~~9jI0UeBYRr8t$lLjk+~qODS$Fq z{CumqdBI3Y7B3Vhkr#om6VEyBh*9-SlR0N#EBP%;lSX@ z?IUXUc2RATQivu(V24A=os*u|A)8}IESmImELibit&sU#++RB75-^f97Y6~72FYZ2 z$*jE$co8hZS9>{&g>KaQ-__;f&3`!fq~&B~qn>d874N6?PTaBb@&R5S3@1G}o7EY3gqq;2bbw z>pReUlJ2sI<=Dv7RP791+_e$0))ToZ zHjuk4$`z@xSZwcdZSK9%5xd<n|HE(Ds@bkpYHw|MZ# zm0k_`wH$d~4$;*3(EjHQZk}4Otz{bP)qyj~H;c&2Pl-MQ>RO3QeIR2i67@A^Mh7)d z@EhW=J>lFJqk|O0I*k@j*!aXgb=vT8ay}UuC%Juw6f;_qztkUKr_Ht9CmM3zdZRpO zWt1%Yy$>{2t;K3g$6swVekgH0J9Y@myZ!kn5`uHb1!T0R@eGu~;FgpcJbJ&TOHMl- z#qr9{w~VR9A;$-Dj-$g9LI$TDcdOqDniG?zm?&+W`be$G5h!-Uz`8!_YpdJug?3O6 zdpU7-_}YZCcZRrt_Q-S0wiHxJiq=m?3?1cUybEGS1GoC`C5EtdFSM0I1_?)7mvI2X zanSm>cXSgT;dO4VI0`FYxUkj8vM=>p3L=thxN4t`=$kji_%bCzQj^W}AnH+Ac zl%6z6mv?(xJZGqL$xh=v#K-wRypU9l=vyAdtWh2e-}_1w7crX?{&rGi5ogd712f9= zApn6Q&c8E(8c2(41cHj=)xii`%w-5W=l6EX$7_MxNHRYI)8*#Lk-p22F2c7pblVT~ zB;g$vHxI%}39n&{laXR2JtgPAO2oyP)}*y@XT|};hkHDk6_)H2oF^(KBMprAUX^T$=R*RZuSPSJKg(y%Y zE1q`2x^s-C^)=DISuQ)0s{$D>-|87@MpB|q%uXfJF9f4Kb(p{yAXiE^8X481*i`(g z$1cK0%XiF9?Dg9h8QEwdQ9-(YUcmFLk3|HS+7xzV77M+6uI|ipwbfYGS6xa{qkL|x z{ljlQ^4ki~t!#rhQ+xkm+dhx3`S4n77ACBjiI&B0Z&QQ0^2&r- ztui%4yqoK-LUV6KaTz@D9Cr7S8lKypy3;t=;Z}2zOSfCB552Hb5onr#$uaInqAi?# zy)~;w8m}>Kai(US)ZKQw(HVj>s1lV!7O6o;D0);(kylf&?N{?og^pTU~&^FPKw={&%yHtD2ULIGz+R5 z00`GiUGS}UU5+JZJS(x}*Ju@)m2@iL_Q`m|P4(=jbj(pPi_i$9Fe=GwldwHdQz6B! zVp*|20}<<6Jc0?M5TCTyef%tWh%4}ZM|Yi_sNF*B=>AI9u4npBCpVH;*J@sf3GlMh zD+9Ss2;RI^%^XRY^Q{WFD{oq}7l!ApLTI>y^~a! z!J<}(buk60sSOzQ%6S+A<5ls0iDv5T9IOcf62;hsxSTJvuKOkHCCs5vEJ2d_|FiYJ zy_zM-abITEdAet2cS*@G1Xu! zdCsZIKM}t;uA(9K_m-4{;%fG&T&C^!q}ODh{!n*oduS4|GMMPNsO1NX}ahDqE-^cN^~?ust8l!SCl(rv&9>l&U3u;q%bsN-=MBCzqH+GO?LdG~|AH>#p^zEHQ`#La?J5XDgYbtk0-b zCv-~Vc7@L-rNw-NrDvqKxTsRj>`kSzUe$_@hKEdO{I*9;H zzunzYYfYDgh+lsFwto2*uc7es#6LrLH-2EZ8Imcv7fmTQ2@__MBzfLv9CC1NPEqwb zRJyEO3V4inw<@qua3VQIXzA>!R`P~7;LkD#n zz;Y5Lx+4+m6KLjL9#eEic1C+2Ku7Cz+m$E%jJfJ4vh2_G$ui_BcmnxQWp?-W7r&aR-}ukr5A~<)Ek=g^Vox< zcf^3PgwQ zX5Jaj{)7~>P890OsMv291LzgTs!OmNVOwt~!QDo=JGrfX!p-odK$_m*;`bkTR12gc_gaaw}% zkQ@^dS2BRnOu_$tMeIE!woLm@VjtK5h*7Hi#8PhP(xBH~L z45E_(f1C&L-~BAV+7l!&oylm4FK=fQEy{?CHjJyRvB(UniA9CL=5lET9{&79YGD$n zQME=Mkw@e_KfC7UrD-X51X?D);$!!Y6|pLZP&mT@zUeKLk-hiR4D;BV8I}=8ZGw&< zq6M8`%ewS%V6rkZf;F7QF}%po9r0N5=jc9YcZ#TJ1Fpxuu5U1u41eGPicLmjuAdix zHdCD_K7MhvL{*53W8C((!z&RkXhm?%lRmve%c;y8+ZTg#muui-nz8TqwpxL=*nXp5 zEeua;H(7~Ka(w?r^v)txAwwPGolN9Q#9Ax8k6hIAA*r&j4d)gaTh@+vcf5Ok7)&|D zSH{T>9*<@?aY_Al+UGTjq%6G@S!b}$|9gDq9Ah?H3!t~ZZa+2*sh*^U#HD1;ByFJz zXuTLHrG{)!*qly$tS5^T#tHb9y(^XCIaA;? zHKSCD@HX!+#XNR|=Gq8kKCH5y9M*`Njeo{&z`)(q_cDys5`FDhr4 z(&nhJf0kLp=0vJUfaD=uW&izeDEv#{KMr5Pb!C6fXYaa1@ z>Z>f|L+7#b4#yb8xI{;FVoT#ZDfRFkmerUnt*R6_t!lJZJp@iP7ptwH1&#!^w$p4! z73a2rc47+>&Df%${s~x3MIbl0gVLNI6M?FF%G6)&rjT?=i^_E+8umn87KO@61J`Q2 zyQ9ggE<{qkzM?2v?rt=?cc42rV$%#<+-ZkDttue1dmU<`PjSu;=Vy%E2cRbXlat zfA`(W`|5oeoV*E!)4J!7rBsPu>GY*?7&A7@L zW`DsI8#o2fnkW>rEEhUi6!Jw%dzTUM&X8NUB6L(=^1PP02u_Aq;LtMR=9z>l&lQ%o zj>p}XC;TJw@|aPnHEyekHk}YDZo({@l2XVYcsd?nz_y(~k!;Vc9Mk!oH+2mnI~^#cI?6ox>eJji z7BU={6<5-Oh>ILsw2nVZ`?3}UnM1xAz05_8{XGBezy0%%dO8X#BK!MiJ)i7udiX*6 z1}9;)$W~|#43be+B&KP?X#fBq07*naR3xx_&+#0Q>Iq;Yfm~Uus^+&$hQ!w@2BYiT z4FGtaiF~chs9N-EFeiYnBTc|BsI9ppkr>oH-+Q>^En^$h(%9#&ZQO*MDMX+GCQj_obW!-4o}KW#^{P zF!MQKFiGgWw7WJ!*TMv?O=u9`soqDJ%|oG{_>|RgD%|b=xkt=H!5F)8D33HESH*h7 zn3!eAD8~8O%SfED({yoMB{+RRlg`>Ik-g(!@S5BEyi%se376*&+!j$+D1S}{zpc8w zFKVWpnR4p@<1MsrvxT2Up2*O=1m0ElbOZb+AL|hLM=e9MxiFYZW%7Pv!W}#KVOA-g zV2vGfdK(OmA%nq4&gbi;@Lv0jJ;qHplelzejqO-FjLHu==~;V>o!UbvG@M{F`79c{ z9nW^r<4jObt}#hw3GV=~BO7P*pTpGwnJ=uOHE!-<0{AlO1M)1(TcUv-TlQ2<`V zCe{7KHgmvoR@5^kn_G)fjMkOs-2SQ!a+53As%A_DzCy_Nx>A(W6lGinZ++kwblMSL zzSbQaILXx)0ioe@5|}=Ja@yM)QI)$s>sC!@Z3;BFu0C*(WdpbO%c8Mb83cRT<%mG% z5_?^L+G)ZjBa@Ezy2h^#pjgVSkPvb*kZxzJ?_-buaO|~{KT_+ksTi9l@EAMK(l9~+ zDQ+gyMi0KZVnnCf2YNB&QcT04-R&Pk8V8<4FrvFWJ4xX8fj+y+RN8n8M#-REb)2!z>+S32N@+F)y6e8{7Sb2iLflF2m- ztV;EK{7v|+^xivwT5DCY{DT7RQ(+7YD9R%Zqu4G1#_H|vRVnEaK~fCyXj3;d(xg|c zb9Xl5)mwh+RFaJ`SD8g1Hg`EivTqgT%0n{GHmUE*DUZx5dSGT1mZ(~EElaIqTLH6AOC?Hj_HVZ0eM8s8uv3UUpJ2~%Nk0>zG{joGl+ z2A|Ty?DvXC^Kqvn%xVHFJcJM?mWAZ@2}rVWock>MY6g%;xltlYDW5j4gLMMLXWRwn z+^k8IF`o%n2XYV;7tRCti3w!%ZN2CJ6XT zrLK&GVj;)3JUHbR^v-zRJlS`1r3J3yV4p;dLxF}Fa(Vayk!KFtH-;)>N3F_r1;ga+ zNVFx`&U2@~=85g?LH%v-xQa#9p~Ci%Z+$G9OzC6iLMcuuV=)4&2aG|TcS&5c=W=B% z(!CLMhYZviovX2`5Kp|Q{EEN(FaP5Q!Bi{asr35=UtWB~h^-oqM=GZrGBjMI%-jmq zAPND`T0p;hw+tL3yot^D^ypQOLU@4Pc$-+P=xDaeEFw7xR4Uf0To0WyZ-P1M$j=Xa zzV-W`{t~}2KdlnE5KE*eXv*%SF&KggXCYe0_-@mzGY9LEhnrxb6f4WCl1Sr=gzIF# zx=qb-z87ZL#Fl%cj5zX0s*6thm{OYQiF|#2uDl%OS7f3IO;zW~x4v$E-CT51FmTJ; zA?aG{6o0g_j{@zO?VZVV-YG#x+5c4KPe7EkpQ!F7%w<9j>xVh!)?+->qbrTC=N51` zDB@klW?*BDYA%Nn!1H8t5U?j}{`{Y;A(5_@J(V43hmD%w2I}RPk z;UrUrX-sv~M@6I11q57pp*CpFkLFMGFRs5dQ*$hQw`}2*wFgp5*NL|+s}zNYaMNy> znNGJJcicHrQb`szR)k83;bP{xqGfD1U2!HJG@@U(ZJyBMk&CZ9%7|mnHl=bZ?VhK? zZCjZp6<#^Sg+7sYQU2n4S|Rx(Z&(dLGoQLB6?s@k60YP=k!jtB_08PS#0mQ5qKG}@ zzOaASNJ}3JjVKfLmT^RM}D}9w89b9!va^EMX zVsgAop}5AooWy(68D@$&#VpK&n@a}Ja`kqU87`rg-Ufxs>E-BAdPdA1d;vJWO@t+l$y)E{DmxL>) z#n6grV36394Jp5|-NCAVBLZx?Wlx}=(v&^L+%)3=hZr;^-in-!fKx$^FCJzNoqwe$ z8PXbOvlG~{Tfk)6S>``06#y`93DZ^`t`Y|HfjQ~()OPs%i+ME<5I)Ot(2-< zjp78h%pcv%NU+AW52<3!P;-Rj9ohMme$>ny7`QMJ>yjjl3sYnG?A_Y@V^@Z zAU^yvkn4G>vb(#HnQIl$k*G`pqQ!n|N?1B}S7g<~-q=k+QFP?{g{&X-{dxcPAAf!S z96zeQ<)+o9`Eq4cbRt#{MwT+`xHOYxK0$Y~jLLOVHwNp;h2ivOdEhubfR?d29K>W? zWi^kca2Fvne$i19I1N(>8-Lv;wpy$PI zJf}3pKKoLZM0|0$$A~jD86>-?Y(4CpTWxri7lk+_oT3*6PwagK$@T$y=GBz)u}5cK zgFJi@>u{Wze2ca&c~N9MXR&@~aEXrYn2gyP%O9V&eW7vENdyj|fg$ayMqKR6h~M8r z_w`ZCMq*>zk5if;=ecLC;7KIMCH(Qq`}DGc(}uQjKlR>YIKv!GXX23hOHVd)96GdS zM0xzX^#pn8|Heg^C0KnjOgRswZ1Pk$ewN{sW;C8)iVWsbRc=DWVZE+3QuvGfgX2)u zSeY`MeW`d>oJqZM&IiwCb=(jr2A7ql5X}fxtInwBOm=4CJho47(KT9?CLf0t+M3wP zQc>2m4w`mal(9_rc6}cwS{dGFt~|q`pZia&1C|@q&A>Z|cpHU3&SXD?!eUknx6rXq zV>WYV2@0-eXE@=d`&9SYq>jzV0RA!ZiVaUhc2*)f=K&m$Ce?5S%R=(}NO?^m$GNba zV|=juOYnjie3z$VL#m%*<20f;9^EtrMziQVvTC`e2mLTOz|O;Y@kYAGPKfg-oRqk& zf{J;K=K@$+EArld@X1%}G>c*PkQ1uY7@m=@ZF4ey8Uvm%kr`TZF4MODWvI`Lj%vkX zcO1hD9T~e#J39*x;532}9+I9E@-ks72b+}K-^>oXCJqMF&l*fC>`TLeX_y?~8Fi_z zO7`mVb#BQD>bz4p)VD2nLl{7q2j{L}JVf#TPt|QDs?gbOL2~5k-e&Pu0Pl+k*czPcD9lkEQy`}i(J?STf?DzI z>2ZFguBF?C*Tuj0$}Cx!m*L%kH|J3MB9aOWjxjLex>|-2r;;Wk3yTO)9LftPxGUA( z4+vzE!SZn?)+#<9RMe;vtX!F&h(~9+^i(hG#4yPw{I!DY-$w!8wVb%SRX0>c6*?|n zcOF5Sw9uJRu@55(8&{12O$4iAJtokoMvCr;*SR~a;l}D%F`>SmIb>b|%aHOU`e2y# z*Ol3->yx70PO7_{?T6hT5zmTMSwQaCn}=&@kd0U!4^IKgO;^_P9A(eiIwnfDsA}oh z9qZwXkTf{Zhp?7n+w<|Q&njV`hp;S61+#)51G`a+lVg=s!?B}-ycw&sDUqe0|eU36GY}syVT|>Xv+!a6ahAGE#f{Hs< z;ZQg|WoImc8R1|)-T+i8t~fE?S?&Q`$`ezOm6v?92*%) z8LgI-b?iu8?_%S%&NB{gW}bQ0Y>5i21hO+bGM9986u*tjpzlx<;BA8v$6xm?>KRrl z!JXbGpDI(bUTGz+^SKb8a;{aM_@Ze;>sw%Hd0bE7V2YqF?)-KF*Ykd)uZLFiR+D%o zCo#~m#T%cvV0Km;oW6Wmi4R+YI|Q*ckWm z*~D5J%1_!~J+U%si31eo-(dQLTV+Eun%EinRCMEQWX7`usd`(ZBA&^*5w=!C702fS z;B94^Oc8$)s;&j*BndlbJ+jqBejLbh z7^N1<*C9ruQYuJJ*0zFk^b@qx#fEZzLd{c}|3&`d13QG1v#N@SC*r{&R72(zOfJUp zaH-x{l_%M#&i;df-s$l9-7PUrxGLya<}X$2J=3kA3vqp0-nj} zF}_Q)yg4S@#OvCqLogu+Xwqytxsa3vC7zGWyAQ;>az1jTh5Dos?TmcPl`PQRK(QW` z?P;-Bm&2>ewBQ84j4JLA=+w|}7a4fDz7vt6qbq7vHn4UAiP{qR*&44_kb6jlT=b^U z2d&Tx_VSgLBts_Rjn2jSYpz&3mB%Dy*N_?`qO!PGs6-_?tV&Lv>#?4NP4lLrIAZx1 z;}fH8l~xqy`+x>$_Su1~$|&@ntz#2>mznwG-didI?7fZch|H|r%7$lM5mg=zPs{TqmQ8WWA z9#}X6w;U;;_Ouf$RQAEE9z#x|`kn&oz*J|B3$DpkQ%0bG&fNTbwe(otggj|e%4Eohysn^kL4d#*!xIcS$f3| zD-jOlhJ8ND)34HaDHiso_l_KpRDJfkk#nGUcdm6zYx=Jl7QSQ&9&k4+k~YP@$F*Cx z!RKdAOIor4{jffGX6H|N6%-|Id&39y|N}>Mts;nTEciPd=0BV0#P5R~^iyYi(t))(3%AGnEWruSG#- z^loHFE}*N4e6D&HJ)OHdOXM76RMgx3?q+9_(@$i)&VNE@f4+79H8%RGtQ9{xKA%6( z|LNDi{fD({|6@OY|JlTqInFJ(3ai*-t!xm-vZ?0P4zo!77NwD6@JiBIG!%a)n<{Uc zHyzl^{5Y|lOyDCMGjZKPiLJ{e0z^RhEQ&zner-f7pb~>nHVj4*8;}f^T^0`DvT}J4 zW{9wXr2vpEqi-I;t%Em|tmDneOr029X->Nc$(@FaUOIM}}$^iFDbDi!agX%F>0dD7I)B@R+v$Age3 zK$t7?Cu36*7gNF_}4d18P00*a`u5mI2{yFfQnUW-Hp4U9W`ln6M<0`Wv- ztmc)6tfb+YPg>m+fKfHGnpi4w76lOnmWw+bY~!A}S$;LM8hRN4* zG?5UoUI;A#+ZnaJGpcnli#q^KA$zQ&5xdP0b=Ku{)UG)Pc$Js zGD1Na8MT&Xw`s4iaK>nK_ZMPEY<)k#CQwS=(>-^5*-*@Fc&8(5;j#~%O=0}*>FlT| zm=N$v+VxkNmiR28L5jhHUQzjiL^8qXZAynG z$v_$!i9ghc$Pbv48~p^5BZ|(i?|=EFo}*pSy165{=2{lm+;NdP5x%5o)|s|aUBjNC zj~fcZlUNmvh@s z+~xoiA1wnz9!i?-{)LI4;Otxo8JPm3Rxpc`Km6KsLyZ<;eR0gpadA^tN)MkA z?|V{1YBH{_6v2~bA?_z~q98rag`!=vda@SPvDYB@TMKgv?6Wcoa8AQyj?dl0g^kQY@X*vz zoY^=glfdyIhuniEht=g;HI}M}-<=z8*8oE-Yht5gp zRj~}&B=k4D)n`lnVbB{I5xd{%7dRR1ex=LlPDIDrjEs*5Kky%ad;a~mim(5e- zUAl}IIhk-))Oj(tFE4tK)ZOb)SdIp-jZU54aZUFSby>#eL^_)mBpPt`hVGHLoV1uU zZ_r7R{Zlq+Ux1eA*d04Bv++eF|fHW{f`*;W^R@4d6lKKx{r>$yn zk}b*!h;a~rq-V8(g5C0{7n}|n2iHsis#f~RgCgN@dmU^YC)||ys4_ih9T~Yq+7}3O z@7Sa_7w+dUuAHA?CgHHtGAAk~hS_PvabvLC7M;c`{{kXn)Uqf`Xi&L+&Y*$?9tC=XBf|pRIER^ge;TqJdOKC8MMVp0}3gjL%P#os*94TVs^` zoNaf{8Ktd%nis%Gju)!vIE-n)XhJS&q{5*L(&hGT! z!}gJBE*0-G>v1!a$JMf&Um(`xa`+!>co1q)%F~__I)=%Y(+H%k*X?&LhLi8f!nVPN z`%TTlR@P$r&UHiiYIu9w{-OmwA0GXQZRL9&Z|C8` zRMaOT^WD)qHljOvV(!C=9Ve&d8Xt(^oimK!!}ChtPe^KeYjDW7&LI#3<$l)Zrti!v z=9iCNzp>xICHLY$bR(&1?vD4%s6aOGm(XRz!zT_dg#`1_X7C_J&}D;`HLepQ>FhZ( z+=U@k{KnWhqfbQbP6vhKK&OTvl4Vze@1*6RDx|sa7x6`d&+RYqCz6$a{t0j)x0r)i zuZ55YW>YITif+e>>Nee1#@+CKX6)K$>YMIAtA1+NWr}u^O85B&`JfiLwK&mV5m`^p zS73IVmMXc_tq(4?#y1r=<;bqZ!6Ysg^ssl#;EM5(_mx4P*A+w+31S?2feS0DrR_lw zx4A@dK|$rF_Eu`|Q>YD^P6a4WpPiZolx>$!Ao}_KfBny|ukU|axu5>g^{mhLCpS$M zUOylQyjJ-H;Gx5nT2+ol}#ntceqqu zXh3^C;qav40+hq&!%VVtT^^pEc>L-(798#FurCL$M$Mc92QIJ5+$4Eu+e4D=nV)iL zUEaLKPY%rcIoa)}vaju&JCD9Y@4d!6CCWNq=wv$JChA|-3hM#yUr! zOy9{P0IrCjQmhUEE=}Dn8;=j!IB}vVt+d3Ia!2e_;*=pb(Ggiejp;C3!vnug(Oye5-omt7ue?0}O2@%7Y#TQPlEFY3*dH-=? z-X$#ch^N+NCif@-HdiZGg(A#BF8!OJiU|(n6whEJqF529oj#)h|1)!b76~2qtJl~0 zKfn9bU8T$P_{)5=541c@gdHjGqH~8M!od@`SfWylwt|i5%`kZ!^-4~hcZ~RYF06SA z%9ibIq?w1F%{Tx6AOJ~3K~%_3dBLj=oh^}(C3w4wMfPeDp=cG!?neAS=|{~Z2=bjg z#WR%)ag8HOtH*7?RbJqRKza;nL?{j$az5w2Q?g1~Fr&?0D=KxcNYJ<=r4osgKqSZb z{0Y7a~-|G|QFbN|%QaTwP6_{YIjy$>it}O6sr5C+ou$%Kq9%*>Avy zb-P(uwh1$NBNMS!Ig&5NUvd)}GuK*+P#57P5QK7S{Z94Vil~yE7<=M}T}a81$JdXB zejqwp@+(i}xdW?u(9z6L8P=v#JYH^K7(g83Dzq?YHn^+>mHffF!sJFF@< zvR$aaVvm$Z{($9!aB!j z-=_Z)ApoXH>DSx`3YE?Q+MyS_KFY(u7L8oI40eX0&S$a#D`#+UG>VnLizA0lJ2x(u zU|l%A2?CW;l9cqRMPS`EPF5aMPu^F;T~XiglV8XYM`y~eK+%kS5w^3axdkpF;i|}m z*Fe18$9W>TX`fN|)YDGCag?h|sP&wNJewFtCCVvtGS8*X8EH+tBw>RUxzJQzc*KQ@ zor?M#56lb&6r)pWW*Bug_%Y-mpdLJ_!=Ps1I$zM3)u$A=lMIH^#M68%)@>kgW?NJG zqW_0B z`GM`;WD3+H)AdnSjWyBUBU(L2|Z`DDM58_&x4`sI&*{>v{^!ma9(s7cWL zcG}XlBUQmx5t)zDuyqY&!7D!<5fctvmUR9duom`q;Tjdmp zF+)l0%~6Yg=Ccr$qZ(EROvXm-DB_JfG(N37f@CCx#{;b$aC++{%0Vrp`8PFTp&h|T z2wDnN5dCNxcAc@8tSfgj9LGyS-=NBFV}yCZMaQ9-9;(g%Px_aH$ckm8at-(O)E(WK zk?ZIfb8a-mWu{0O(zLyKtwQXPI3j_GaQ82Z-AK>TIOdg^+@*Y9X+0Bg%FmEUWTKvI zyNElGR?Ju&=~iXdnv468al{j8b$7$*NuR<@pDgFL0&q7m_ulAf9A zV%e5PAjH`$JN`t@)$Ii)k4+0u$9`f$4`i6ScJEWgo~;_)H0stdvYMf9Xnjoz$u$zH z%Ya%(Jn3SFqnrTaxD;NMKLyOrPsb*h^<0JxDzw=Yuf&)3QnG@`a@1iYVwEN+pqMkdo^!X5OA^?_MUhW(bP4? ze#30iK;u2^gJeD&qpopz6Vw#V@OuO)ITR!)*)gF<6iP%F)&!TpWIKOmP~AQxW^y-Q zaaEKML1$NlnFj7yp50q`7|3Wpz{~H5O9-BI7%z}s|5I)$IbH$Ih1pWh93NMb@K!Pq zaA6bK%fD~z=HZO9w;Z@q9*G%E%5ooQjhA-@yA!cpg!DC_G?=}WRH7y7bj4|HMJv2O zI|IETh~DV;rw~5!#^B((!yRxLS~bUO4h0P}7P24$bLFK*Y|o?rl{R2F8Ux7a*w$6y z?3p4Xo;;p{WoNP&v>iB1OTNNbOD)1kGULur+#&%*WOsq+|8B}oB&QXRJn~t5f@KYq zuarSS{eEqH?aIZ>$fSU>w0zJ$E72LbB!p}1(z>}~Q4+Nwev2Vl6%2DsRD$E5+VAFq zHG03jl>$)6T)U+*YBW~oy1Z26f>N#1oqN8j9Qzst;H3O?x6ZMXpiL1VAN3kJp5D{l z^Mgaoyrf`oedy@V%9XiFsX}=B$8UfBr5JnowxcyBkCz@x3;O*>$o2TTiYMlN0!90c z-Ui}veYD0%$&y2Ba!x>>rRBS>gawb{F!Olo7cuM76==LaTG3U`PT()kYy-}UUSmkg zTF_I#rVcbt#E^dD#alqQI zz_Qvrt+!%&gq~NU0<Yamsy}THaGcC9{kmvGJ{;2ds_2+`&mM@Ur^URd zSq-d;dMyW6W#U31uzSBVtkj$*Gb8ujPIN{C z&Bwd&zF(ZIRaC7fjhE{F0y>K$;q30-Up$$gwL&zejNWUlsM<=LWkO&Fy1Q%DTH=s0 zQ?H(=r(Sf#UJ=O8xBmJM@!$XVKYh=yztU|+mn5>Q2GeVAr2Qb-c6KxYP){T@lWqlj zcoFC7p(Cz$uZ5OCdt1I6vp1^WHb$^WfH+6t8MT3$7mwEwfnMON-ox4+uC=$gEu1D53Hz4u8%a0s>s6n z7Wud-UCOs-vT@eg#!wDNROotQk|2(ZWk+sli`lGl1^W3|olEVngpOW&q%@PQaOv1= z=G(!}aYd%(&1Gd;IjF8eD=Ng!jk@K0s`kUA6 ztH0MGPY+sJaoK#+ROW6`h=QD_l)TyG;=?-P&?@p|RwFq(QiG@eVaIMgqC2p?Xw9)t z58n$;g|%vFI|s419M`~z=QYbv=b)5LT^5R~49m+kBT&hq$y%H=!P)gi1s093N3yAM zs`6~CMzPe#(6NYUbAYYfw7`0(6PCojLfIcS=1znG4l0jSoQWE$OG~KPo>gL<*b-kZ zk2c~Yv>$OwcQksxil^UgG2QtJ&Ul#zU=>qeB0L=7`eAm4%0_NgZaFa)hDhr>wLXpa z{u4S(!ZPM5m$&eV*6eUP_I11=YaRIkX5{3imw1#t>j&RW(t9@g)u$SrnIbdyH7m!( zg*F38t`n3x$L)WJXcD&l)Z54CWnut~FnQTMb7$Up#Xe5gI{EhR*EH}Ep=^;@Mw->Q z?Bn!Ovbts!;cwz5~&sw^VVx*Lt&T5VY0P(4p@11}>V_|BMH z$kA|tb~4g$WEjXsBN%|R1G`zkah+{L_g=&aFoEe=U<@%IUaG@4Jw`klH?Vi-Zv6JA zr@k*(zs=84C9^z(z{!=NY(^)3uF^29a6)$_ciGX1>N|2Vy1Fexfe`l2PCYKIP10AGf5ls;x9aj;d;9 zJv9AE1CclsyGV@Cs4h(AB&V&B{~_?k*O%W|E=9JS9t5Um$EP6mQDFC>zwWnpd(7Rj z-yj+d3N9B}1UNrA}TQNNXECl}VnC=36Rs-rf5v@`>Jf9hS-}pE|TN zf=%X!MfJ=&Cp}9wTw5GiqZUxFA|v#sm3N5f0(&X{#{oR4XFooh8hncih z`AuFnKh@n%?^yPC%skFx-7swqj)9g&ZpHwA2Z^Fuiu^t|IC zp%Bq|!ehy`cE*9mrOV6h9nq4+p)cG+>epd9L)x6OO8m2#)nP%^0;XdrZEq-t09C+o!pOjU5zvjGL3(scIG1m!qEOy zQ3NEJ!E)@duiaV3N?4~6Ibd5{m5OP8s?-RqC-PAhv`X#kda4$W&>SoaQ`RjIp)U`a z^(@qic}y&4Q-?we?rsgOR>qoakuk5ejbqY4INqFrAco0>O#W!Hok%lYmi6KontEEX zSrt6mjVF@Jq{yKAT|(1G{xgp^q9+;Gl$9qChvCC@zpFiQC5 z0^F29!po8(5PJ@Rz}vCW-Ft6cw*BM#_xS$X3jA(vfTCy%*AtMA)spXJq2Hs=*>cC8 z%ZATNJ!Ur6aYYu=QG~J?{MZz@3zk|HZ^!!vGIruxvm7}@RV>pMK!;`HqT>N1LqmF!Z1`m{>8Wt3cLjrH8lj zJ0eyt%9U5;2KN5qOs#Z?5up4Od4Y(k)tmS$D|*3!0lxOzz*Dtqc|pFS6D{Sw zeB$emtoM1o$G_I+aysjbSjyTqOr7y(kwkEe_Wi; z^(ZK4dH8?0cVcSi0VA<7*h%d0ld%HXQHeE`rMCeU-lP%g5XahwPK{mCYKxXrLvw3~ z_k_!cayJMYi@Ckoi0W7f{s49P%^trdY5WzM)=FJuy(h~+5Ab?M>KwFk1fb-Fj^!T4 zQa}fw`G}Y}LsEhGez7eXGrZY)N_f)5t&WkTWf&~^5WoY7_nz;UqV9iJ;b(mM^m@(!J< zquR~MCKyO(s1PJaG!^F)l!=0Pk^`fQBN#cB7lY8Qf|4o~Z4Q|S#83mxWIcW3#X&&| z$^Jwj}cISeHpTMBT-vTfYJfjOJSZG_B-dT`d=atUYl zMOg3ylQA`%lX3IXX#=V3w)!e_&L)jERGl6tRNb8hpMfjf_PKr|a-R#8s3izf z78I|2nQ`v0tIV7koPn2dLJJAv&1mN?R!(yy%_|=p%#kfYO(hjCKB@q&p7pKOd@dG&u;|nPm4C#0r zPoTC?0z<*5l?!Vo$4ZP#%;t?M?AU z$23c$Vvq#;4$9~=3z=LdHoFQ!p*mKNFS->YY}wht3anVY1A7Z1if9igBBLt~+raxg zB8RM4)9zlG?dULR7=%|l*M;?NWv;Bq*zZpLF_Gv>{aQqxa{E_fh{{lqFi=GWl?|Rb zNS+K)4^dTF1p*|ikcs_7;`^UIS(Uwr7SHibjgo#;%~8|j$V46$Pw982+>{^gr!=AA z9DVSKIzf+3WX5Cm-F#4zL$pmJnWJ6I(X~cO4n&kF z>|hvJE04y_1-jf*(yKqx{$(A8Q)lc3;g&!Eyit@Q*9 z-)1eW!!@bN{BW>VIjA!nFlen7Ms<^i!;xb|&jHxvxq2n%%jCLSJOnLAGT!PP>remk zH~e^WRV6kDi-vkdqc(E@R#iRgLB#G3U{x|6*hcRZU%Lu{wH8U8 zD~l0poE&7ws&$ls5m~ia)RXMk$jm1L*il(oS+xUZuVhiA{5)&__;qo+V0GBfV+$y<7LK$WRKX4fb|}ghZQd3plwK5mMhPQ zMCrkFp;tTtaT!PKm1FmzC{@K{^U^FIGA^(u)0lAH1!iAkpDb?q;kkgyWR|#I((N1k zh=gUeqno#{#8(_%jL9^<4}9k^5C}>Hfb7as8x4O5T2I6TBZ!6&(g&USOe@zBXIY(_ zTm6hrouxW6R8I&3Z%G!wUQ^J9GfTFU9&{yto*ZT*4}|i=({Y%1J<<<$t56Dh_F*DW zo7#=HmA%KbIaarmKNoH3xIZ^89yvP5Q7-iL)ejQ3^~^A7X8z2yKoxP}xCabY-X{kp zs*q}1Z?1CwqUboxzC2^TtaB!-PF?yucb|x$Osv}<$f{^M`OD(#tubPfem&4ZMt9;p z+0I8R6p{dz|1fPOpTx<&b`kP;jr@be%#Mz>GT_R5L2%ze!^K5 zS3r^WvS!-kIIvv!aI9f|`d2_JBGqWtcZzn65*9Cem)B7c2tHMM(9s+SuLgZs_ErM{-@A+MCoRz~`I%H_@0R!!OO!zXn44Hg!i@h%T0L@r^#S~)< z4E9%>I9>M80f!qJbryW9oQ((Tr<`k@gXV^~p`+CrMKl;#Kcu||g*=mC&S8UN)^%)3 zgOFE{b<;7Y$2jhQu|5cf>{C|(a+j2|a;lu=o&i>Z8MDG>PrKCWaofr0tF&B0SRr$A z?VHIJm4G>-fO9@ODxRkpyE|N_l>QhSSc_x{Dd+?jLkGIh94HsF4G>i;*VEn2zFpA^ zK}BojGYAH%o8c=Q>hQC>oy3%yEAta@p};zi@`}vTy#|jPc-pplaK?>P)~d%jTCu@x ziC9~SjM%^Y_I-V;z;+hp%pgrjvjhS(2X1SdhdRJ4fw-{#tB#!bTtWW&U!0M3YSag% zknY%9jtfQpdY(F>k?{jNNXC<-KC$UZI~E&ew5*ZML`6e7B6;aZsX}_pxiq+BRKGBlF1N zObR~i%XOktCuB$jMrXjt4wR8F?aYR%(Z2*LJ3hbu85<4s=c(SAgV9UT5X%ZXtak;37m>pMk^PpV2 zo1HTvUto7<=I41-dZ4o^D{JqH_)*{c#ee=2zW?!$z&EU4d9>xJQOw_;UCseG=Yb4S z#X6i=Khh+JU+JCO(=_VzaN&Gwa*6cfcScj;99NvIU7E(W7*tkcH`WTeyr{4ZickEYs&K#MCY%m9x%!$Ye z8rfGxuGDMCDwdEFvN>GI+wB{t^{-4*Ewf~K^(IR7`;`OF-3`r_tV2Q-qJ;J1`zTdN-lcL zv_d^EhtDsky{B0eP7Rv8Sz6!oWZ$}?D@9v-KKB!bW=)A`t&_}>b;iWYKZ9enyiCs{ z&|BVkqfYTqK1NeC>mcn)PTN9!1v+(j9W(1tW16ZI@qbwmK0Wqd7Y zaSQ}1S5+3XxNBvuMvg9r_QiI#)gD*#3%qiAYys)9R<2L2(ng~Zy-}4wccb4Hj5ZS3 zRhL$otLCUczI&$#ezDoXb2Cr-HHE|c)an_ak&eJ_VDHV)<0#smrQu%vzV*T9x9?Vf zw?+{x5z28m+NV5+S(h30piAp}<%>qFC!S>i(lo%zXwPs`R&0<5z#D>U*fr;pXFrWI zZ$q8BExBM+Bf`O_Ec~3TXw=m9JH3Io+d|N7h&%%Tol1}tUvYb`+}e)ih>pnfGH-Jp z+RbV(BrsL2f=&J`B*z;A3W2iY8cyeV^z+P!&P0$ekNmwG8Nn}FcC3k06O?FL08mTZ zj@uq8Y=vE3b%Nv640x2g235z6ap{}#kD7EJsqR;#wF%W8yD}PTcf4PW7xyv~Ql9T? zS*B8MGpVBSlBneDlW+*?$Zx_;a+_HiZs9CLNKr}4VQ+cN+Y?&g5MQnR!`=8G zqj43!;rVd`^E2>oq>Xu^1;*}}{>Iyz+x?|^c_)B;oEn`@j7+bl_m#;ZO(Tj=yITMN zAOJ~3K~x>%2My~YvW>_NL~cTW?fCHJ_e#t(_!IbM$jLd|s;e;Z=0wg{f+?KOaxW0@|0T2cjwz@&hUoPbtqN_*#i z&nl&~Z97%82D-BZn|vc}&UwOT*^>gd=soK-H%qWEPS95dtEQ}*thiT!X8!TrsVRcA z#EWa`QrOS#OVB%Ou_KU9=?-XpY*}@*^W-nFNanYYXO_v=fvExg*sbfd9V=t4{FKkA zim{nzO`eklP_{V>!^(qYX(F;JYDH(h{FA?Fq_Vsr$~$CeVHH+JZ~BtpNXL^mJ1bv! z@kMcdECt$C#D~JIPG|>38!= z`162gHs>9~^-(~|WInAG`DmLoK;YvVvv`od%QkdJ1OSMN0!t0wtv(8Qf+5OeU|f@( z%P^q#RCzp*g&IyNwX6ecqEKA)&(vUEX9TuVTFGk5BK`*M zuyjwTEpwGIk5+G8E`7-^DugESV}lLdP^GR(%Aog+SG)s^JoRCfQYRklXv83>UR$=k z?fE)*`Hw(3XD&eKjG%u8JQ&a1+&f)13YkGh_f)BN*Kk2m23wb1hANg#_%KeSyrPDf zzAHDJN7Z^AaJxD9lV=7tq$c&#o=dNzN!{qJR-c>RC*$)-bWlsvPf||jC-R}>8O;^Q z3nMW1=g52n2Q^Dxb*2 zo{Y4PjEYB1d-k|$RzvQ8S6WFs>u20}aBN@D2E3sm~yy4ENx3ValOj9LgksXK#MvD<0$t`zR zZWVeQ6$?(k&U!&hwx(R_A|l=oIxDtvEjv1XGPiFWoMu`zh7-->Ei;IzrAqkPUP&Y}qPD~`^~8GOaxuwIegxQ3$lwgx zV_eE1qt+~q^N6Dy$CsH)21R5F%VsrFSzDpwfV!sGZL-Mno8yQPZ>$l$_K)0u^ye@C z^0n5t>i2JS%XrbSGVv(&4DP zd_h}Eio?Ae(S$U#lX6+XC814F`8?^Ru^ye{utQvmmQw(o$nKGLloY%UXUcR#o}W+# z@{a3z&vwi}R>}X?%8r!aoYUS)&f0USZp)?KgC)qj&5ef$8tG694WZh5;O38EAr2H% z#3j!3wC!9ILJih~{CNi?^%~A@jZ?y2CtC%3$Q}Wh5!e^y$Bb612X(&5^iV_(ZFGOj5Eq?WZH{7-h6^ld7J^8(H_I_*0L>1PGJ6T#k7+1}MBQBVeC`cG`3Dhiw z-Hzzk?yJYh|Hj;R8I!F|eX<8Yln^RnH-T*>EF`=tn@%}T%o(3}*&ZSX#oosR!Ie0i znv*@REu2n3s;hmodlu~S9@|D9`Dndpla7rWMH;Agb=zzZ_s^K-%??IfQBr!&){xdD z&S$&@dpSO75;D-{YJ1jXzvY#~CnGNJvB70)$yPkjyH9&>IAmo{xZsRb`mCgY~ z9Ul6`4)FYp*lFmuY%8XcE z*iar?DUU{8dVX9f+3kI}G%ucL=+Y6XJGG~=fdz<_^YQ9OV&Qsnt?HyjYCpbxC>c?~ z_G?8v4tmjCvL+Hsd&-(XEZc*_`AM8!>?_DkUN#Hk#Y0x6jB>)t-0Y3>Lk@8uS2``% z)hVO`;8XZYOo8ZZ1Ra6~2jm^{{BFDl2i;-J1X2R*xhO~gnwci91xCb49W(t$`SH1< z<*!2r7B#_+;>7L%)>Fld)6FU>t5&TA#z?hvnNav}f@Ch1t=JUEMKRtY1n*7fy^fnw zFK5?UQAN^X)(r4TT&CXB5?P9%_=rQ9)|ourDiOWAmJnG?t!Q-b?v25x)S~yUbJ^0D zl~FCXps-?le?=oR-rXSr=EJ9T5cYG;?8vQV85*&?{ABDeXjo3E2EG-lAjFIMNqsrjr%9oJv zgUdr4fZmB-zRW*d$P7%+nXA4w`uK_`%epsvQsPPDh#uCZjSSUK*`N+}$tZ6{hi!*Z znWb%_(wMhGR;A-`@f{{NcArSJAmrKV*Z^T&5gS!{DN3m%sAbOJ$p28LPbPOqJnx>R z$vsbJCt+G-cjZN+6`PqmPCO?@cd2PmDQ@&~NMdXud|7m0xANZ{yRgpc3_oYLR9Snq z#tWwpFcb!WDAZE%L^x+o?txd<{1q&v#zxQEpS;}A?H{FC!7FXE%o#A z9yjGc)yzGbke;9_D>LHWbKLDm_0vF92h1oryMF z`5ogz%XWQ`7Y;om@Pb=?(F}HZSS>Y*7>8Ywss|od!rQpl3}%SQJbj+Yy7M|K z9snmjk_Wh;TYh`Ldh$*T`5T%PM>0C@m0ja~iU}U$MRkf{DUHXo!QJ-pD4Qx7;~woQ zQ#ogLw2F+#BXKXmU~4qoJ?(0!mBWxL5Q=ev^ff@|WAnV<~W znsEj5SbQ5ch=LvUNoScKSVoSQq4kp<8C+$t=O1s!sFbyCRx9v`N8*lSZDYdd)aC=R zO{}+z{v`Lvk6qoPr3UQ!3U$U0qX!g0Kn0uK{_=Xz8>5LKoPI|C=-!uLo^hKFjv1rd zEm7!ais46^^d$|^Mo%YeC*AVgP_7t1RCOg1g}AD18);b|jz8~;d+oGsTo>cyAP9Il z!jrRZ$-t5Vl>1jG%yv>5G`*@eq*m7_d-vB@*M9r@zHku@T*M(NLMU7BOX94H>Ynni z2HB#;`Qb}JZ_ttBw#vYei?WC~dRDIMz-4?xxwNU;eV$+xa&M2fbm^DZ&O>pEwggw- z1<1IC=CIh+8wCqC_wnCW9~#f2ikj6%xxAgyEu!{BCeISSM)$6lJ~_67%wrfHH;|tE zSkM5mHf=rY+;2@MU*G18jF%2Bf?Ib^_}1Xf=x}Dh9)MKYuI@#uFcn5vmU_r=s|$E3 z08s{6dv~HKYdWSnFSl)TCPT@PW@WoZbg;)Zy$$MoL`E?4kzaR{kuZ23rbpGQ&vZL) zD-*3Q_M4*KP7OL4$Hdgx@D0#pRK0I8i_y1Lb(I1wTT;b_o)fDUp8kVPiv_5PL`a@zwbHc%Gca5`_LVR^lBP0L zvlC@Y7=f9NTCB*&Cu}nqFyd~vord|u?F{&q_SoL#r2&e`^%Mt4@v0;J71skjV` za9bPPtX;qgz0=x1EMv^Z18a0`uayWKS?0)ZB+hiLaI{rDQ|y%BHi0|ZXcd1O$O{?#a&?y9pqFzTqipr&RrD~b86H?TqsWV z++n{lipn8>9VcYkl{~9C7yD@6k(rxa7+pb*yGPDOl(oIu&RzwQUFq{-f@Yx3ixwS) zva6%**vKHBfrx&*UH$>uGSUm@ZoNijr1x414K$Ym$-s_3R&4xu{_S1=&(e>LF0-0pge#sFbYX#l^ zAF>K&oicJMw5~nRfhU9T^|S!uL4y1DfBqwX;uE{D5>cpPJaNK;bhlypB@>%Pq_@g0 zXB*!DRb{)w7iSE$+BD{42MAx`16UjigAYs-R@*9OeMB{_BpN%%@d$vuEi@*ywJ?Kf z15bg-!|=Of33}nz3A$E32mBi55#?@^PP#;L8PxApV$9Oj1=fTsHS0irR5U-&-zDE3!r8&akL&B$&$V`je8O&MZ-?ci9NxY&Pk}Xa6Bf~RX9nQ>mIz;|z0=jey~4L|GHL*EIT?bhNX`s+dWq`b5(swaeP*G~_S}EogOv*+H&}HuM_Rk2kubl321>&({lMu` zl>06by*Fz#RNIIyNSG6Ji^eif3+lHiZE73P3@)qrY*2QcHv$#SDvHTsi2)|BW8i8| z0ftIdvvGkFwLPR38E(=O`|6*kQ6POzZ1+?JjFYt6X5=}7PD;qK^0q2)4IJI-uk-a- zQqj06%@Zx~k;Qbyl(KcvqZ&2T-%hb!>ZzXXA_pul&Ab-c8hos7aeJVaEfYA}pQ9n7 zsr9|wM*BEcM1m)6q}zqol3@OR|K-gt0l#6`=j-O`bwwDeauBWEa!1!QRt;(|d=UTs z-j%%!oS=+5Q&&jFreDjw@5_-{==9L+a+AWU{&y(+YzK3P(h#3y`px+3K z7Q@7uC^c_bty)H`-Ei@!s7x$1$*e@IWU^|vt{}xmUIQg-GR(7{OwHB367Ihek#^*e zg)FSLF_dVu*u|)LxdWBC)>?iu6fc}-xhUnn3E!E@^l}Xm2V;aOE|&eydK|=Z*N>Qz zWW$66ps}{xLDt?xtd&I)>A6x4&p>iscs;K*h@JQE*H2_7vhbB>B{(;9v>9fEb5)ag zhr>Nq?2`sOSCKt#D_-JE)JdZD`?WmNI$l|}@+XflM(RdkMR#0*VBWSg5`xf+k$Em! zbclHdV`CyXJv(_DI)}(aJ|cug+I{LzfYrY>Pd(A&F3fP~k^=gx&DMcWVU%r<#Yi8I zS-_|jDUNk8snx0L)8G_9;ov1UnVFBLSs}Bf^!j-fbKRmx!93w`yxv;CVr|Ipy;71; z&wf5{Fzm-TbIOYk`Q-Vc*?{=q`M~vAtvt?|>axv~U`sRXrHhNRhehz(vo)@fee3Vb zq&=d8)%qXOV_c<1O8_>+ps7L>nG@I4BRk4?cEuyyvfivA3TTQ?(bg1Rga2TAU#l5i zJmgICB)j9`@jcqcn|ZVqouVlY1P>3RzTsyuPz@YY<;6*IsAi)CT5eFMVr%Lq^ngw4 z=c()$@Cp3<%jfym&%>|v2mu|rurg)X_G`HJ&O{(9V(*RcFoop>V#kM?XP23lnM88| z165V^mTxRcjEhlnlPKf7JX8dWu>!$d*`5hR#D4i!iIb6i!DQy+yzF=Z>|g)&Uw(Ti zpdgk?J%tsg5r6Cm0mq}%rcwk?z8?>kLwEdIv3$jL`X#<;Nz~rh+A|V2bmkn>*~D|JMg`_5T7n9yCNJegARPv=-D(>lTN*aM0%DB zNXX~hKC>o=hgYYen8>3T?D0A;sZpt|ymc~>`L(?qfW5f%)BgHjTu&SDaA$|GVZuc4 zZHTK6gS^1+4aJT00;K7-%wrqF@jZ?W1sAiuBLm6^U#hNPj~AW{AYS3eGzK!pE;bIa zqDw1le?|=f=XE2m43)SgMIFl-VYHGyzC>hpygXe8c|f5NSdZmf88r@JGAB^Hkc10x1Yc7SG z1|r(&V+cNYh70;_4(y#6r*t%9-1I1%w`_3P{?W208sO@qMi0i)jNZcvMmy~d{JZGl z8ZP8DdW&9qPa=$@6CNwiYBh#e5f&B8BkNn+-tv{PmYpx_;sqX8ZIX;kvqdJkEmeQB za@kv>NASs~4QQofQnB=MljyUk+RbCiS+i?#OT5OXudHqDK^1d}xI<=CR5#DqWxlKGak3`;R}a=jZZwQH^HNa0t@-MSG~i z0jke4Z(`3lTYsvOFR*Z; zBO;?d4S%-qwU0stG|T6Zw&FxxQ00q&H7O%fA5B=UCf$5uULOSNHEaV?cxIewuJBzu zLZ86$->@P+j7NlnWgVbZg7n&L=_TFZiWC8|x!ioE_@etC-I|-SXZYd==`}m48AK9= zSPjN&e3!|$8}n%x1JgeEC*$4z52(DTI6qAGlgZtCG1*$sr7-n&_yMt2%Hu!Q$K}j` zRmWkX-&(Q=i#h^CBxC34bC974eX-k5oclhPz<?M_J7+ThM#ZK}keKxB zkX;EnLq=uY-3S^91_S1|hcFt(%w;DE&Z|jf0&PNj3(r>oQNP;io!%d>*}Y`MHnZKg z_(n~}U@1uj39=f&olS6rWvkc>Vn@fGr=OWmC)avLa0Q`TyjN#N*|(SDl#G1W1={my z=4sRe0ZV_WwqA2X7p7%;XgV&a2hCdk6Vp;A4vI-f*~kpWY^lFQdXu$){tNXAsS^OPd)a9GLx zRNl|{V>(96w~Qg{<%xzW_)^o?w-lK&%BCH7{86OM0=~Ze{1fjRV8p{Qdyh0v&MD*T zfQlx9pW1R#RrLkTS0}kc0#0n)fvjKggibO zT&qv2UTa5AHRXR=F_6vl?KF^9_cSUFmDBWs%XFTx3N~^>f7CO+x0~$+enf<6-Qec6 zGms$`DBi*)NiSgdT(++GX?aLG(i|1}IDu&mZ5QV$Vjl6VXs?^-L@`XwfOza^a4wWO zO1%4xVy=~ly(?ImNfw!u46)rSJ9Z)w*qc~1@k2@4PFsx#eyvnwgZ~vG#|+9?h)?Y& zR%Yy)nF6@WO67E36)=1Kv^25bK`|j~yZm>MX1Q3%J}67abZ1CU-;%kcl(| zyvGxPy~TTD1Zr<&rfk3Z*AgtQTn-b#yE1$0X zVZC-&s|R-1>6|BZt(eygpw>!9k2jL7j9s41t6x<|t|#~_@x~D)hB;&!miP*HQb=!a zlsHdapG~XsLb4x)1o0d{b9!D53!e3#te9kQRR=GT^qYni)Y(Y&8 zGG(Fmduvv@zGDAZy<2O>*n0W|0~Vco-h)f5G~`z8{ac|h`l^G!QX!J`Pusdoz8hG* z*wJb+f{f=m$DhxI)yyqku@bF>@Q0sv)J!N`wqjO!X?5i?rm34a*eyL08ut`dk29*h zXDc0&Ons`-%y&MPrlEBNm7=HFt;C7AU@*9T1c|Rd{EvV8{aa$jjevC|@xVgF3S43~X9l_74bc%a`y>f79DjR%|8M{D=T*N#Dj?T=%6QUjLc^?Nv~erf8CN+vwX-fX zh0Bu%gJaH&sH9`>dMowv8w4H^-1CqH9i zC9L{+G&4QTp$T$UEAYc$VU1#cK?&ZN!}$bUcGr|00xU|Zt`P5t*zh7Hf%^eqKZ*u9 z29}_H((i$qYxHIkP|46m@76SJsmOIjUfZ+$12yfiJ(V@%Q`Ilx*2|GFT!bfO%Tbe_ zYuFav!H=mx2@6HoZ&sTOvF$mZy$2^0D*H;SJ_F@F&IN9w5l{y3q^5_|i1x}W($M1C z|MW&}R|6o&M3>E*!AZO;lA4TodW(hFEqat}wh-|){c(Ec>LTbkZ zk8^MB2~Zyaja$Bi!<1gVTjX}Y6?t&7iK}nv_`1>4W}e8v!}37_a09WGNkJP4Z}xh>dg(`USC>gSOt#iz zlxkFR>TTX!ADvO!TX^=z&5`cfBBrTe+UZ08NH#NPaVB zzk8xLv5HjyqSDouh6q)f*EM5tR@TiH(u7e=}aN5p^1FXqP4qJpYy6~hj%*+!n34J802xOmdSp!Tx z8FfnJbsN#Pb)B5a(~oS1cGZgPEect}U0ld#sny7px$>(VJX2ngMy`}5ESa%ch!q66 zkLp`Tn}at}RBNozyMCHG#;#gnVoF1pk_@azgchwwWdDG^KgIKa0&u5^K8Z}O#YAyy ziqP33KFAU4O-0xro9h4r*Np=uK&V>@Q@Cdy@=lxMpp1bwUi$M$c3{w>p?Op#k; z8j&1MNUYzZI2P^mZw$oyQhsOw&ZJh(`%R_ARrd2Vn*RrmNcA9e-JymE7 zs-fZD`*mzyHO+{HVsJw~WUn||PU*kAAWlhwR!Zpe*CKxuYjq{=Nz`N~J2nH!)ii>v zIHn+Ff5WVf9A`zNL4vWK*0QE~3~b*;>!8qx9Se}?y^~+DYI0RGP8-i4J=S4{1-6MS zojee+&L+SqJ|ixwh`0Sb$&taBo*GoAv?HRp=Bb-?+!2li5)$k&+ul6bZ9-{AZmq9! zpVyAas*(;!n92#|_iG-Q1Nw_#*?*u>XzQJ%>Otead}O2HPlFb3IfG-OyvkY*zIGv$ zoV1T1@-aq&#YRtQKjUUc?kQxfCB`0$_krG5`HrYq8DCEj@%gwx`b<un z03)2(WHkTSf;@8D7RZ@cQ_!m|WV+TjW5pB zw$Hj@I}0b#|E~7-T@GGJU6QvZPKtEkul0< z29lxeXq=_47{7}A!I;Bmnr^`wPB$QRZnsjX-YHdRm{8L)vG)?ghf^O@@lL{lG;FVP z5{AB0Jc?hwb6d0?+<8U1F;TrwHnCq5YHPSox^mM&t%$=|{m~57-A&z~MC;sl!!&No z-zeQ}>xyW*Mm1On7fm`_04d7%q)JQ<4LZxGAFaKkM4$Q!55;>|>~L<1CKcZRGh=0H zB98MmD$<(LUJ*M`$Qb|8@@4C;-)<%i$a4;2RB8GixvWc^0+&5CMiR#)?0H1=hO;}^ z2E3))duPZE#dne#q(E6%cRx9MmH4J-#)-P--fS%^qwmnDc&GMg3r{&97iQtAuB(Z4 zY(7y>H)!uPSm%Lfe+^@m(g}>q?D~FM>6#7LlVDQJl=Cb2zMK1TY!v(2&Bi(=$zrUr z+cXl~g_+k`JdE6G?KEwxF%2a#j?iE*k*C2>@y2Zb5eM8o;Z1{zCRlQoSyCrDfb3LT zlIpe&?h57!I2^~X`X0=JYk=)7ALH=1lDoHyXAIPAvt`VZ6*0nC9o1_q#aiU7HPl7R z!(k{yM{LZSRW1FMVLSS~%|0-32_m-3SGx*!#K*II#xUD@JRK3-u)}us-V#8zMb=zP z+-!NpHES_GDd@BIcLv05b;+ivXS<8{`7L@9-VzM7th$M1{(I=SH4m+Ud^&s-Eiq#! z2BeJ`SP|a)bbq*AQdX}qMy&^U-YXsQe!*0V$6yN~7X9Y=@Ehc&qzM_~&@0w1qG+DF z9~<3k!7UsT2arK44I6RKVcn^pKrnl9^upj$&bgaGvQ-xBJ!DIwbYIs%r z5qnGEfnp)l6VEw;4nmS?6j(BlXC$=yt>ALnQN>vFXJiM9;@yrpw`Qn70;q0QAs1nJ zRI#wIn+``)Lbn%dY~Mt2MP?c^s||*bTKTo~_{k)xrg)1CmXDK*Oj#t0wu!la=~AaL zVhl#+!djk1c&GEJ&(A;P^D{l8N_!k8+35<3Y&8F@Lz_>p&QG@j_sIlf26NL1jZDp4 z+xptvF~<~lKwq<zZaLa#FzBZ_^AXfQLSPA^u%fxBb_`X9N8Z7$_8BH z+|vPfhJHFMCK)nbrUPu9EQQ@y+I&7e##3HyYX~8W+GJ6H*!J7pi)X`z{SDR-n>oLQ zB7Ca#X+8&vcWzU48C_fQ>2@V`HvP2G#F@JbsD`9a@eVT#R_hn+$H*H+63v#MO@CJ{ z7urqZBOJD5$Qp28ek=9UsyN_+YqC0fR2S4pR#9{wcD*at;EV%kj zb<=&?;Rbe0OVebI3-u9R;WnoxeWSZi)88%vg4`}Ewk#S~t*q@52ULdZOkf{naWZcv zQ5<&|L%8chqfiEbwEeb1KB)Jd^zq@fw31W`!((JJ`>?tX5fC9}FPF}Ki#SZq!83^mshseG;$$56}!v zO&iJKVlqhS~69y zJ_ZXs2w$}Dr18i-QqtAH!wrc6%#h<>?Q#{!F&{P4s~6X3=1_Dr11@2gCmLuZM4&Fv zVAXMBs+N#EBt0nMD0X;d&dp-rG6irCtI0nST_Yj1r>QG7O}oD}SBcyTt6%&f1;FDi zsi=AoHnvlr_D;v}FQSnOtbk~Yc9&k{W;5#KLujA#y0R)PYGYlKGx8!=%c(n}&GQ4W z%hkz5?MJAv)}%J#OmJERGSRQ|5W@IO9=V{*;j5K3h=U&=;@3W8{xgQ~E6;DF8_$#Z!H$YCUbfdbOPZ0<3 zd12kW!RFZU@`NX00HRHpJB+A?IioO>KC}+LRXykZ@>+G7iNUGT6%kWJFYL zCQu4~j9pblXX#aUicIFhS_wp=s`gv;Zf#;k%rYmt`OA^qjJ>VtBC{$Utgk)QtNKoSmiswm5D4PLIBPA#%Zehze`%tcZ15d?co=uq}q zOPbs|hh16fGs9m`D0=Usn0w=nDw2X>%rQ4`MG}r^+I~g>ZTqQ`2!u;t#ln5{$9+sfKTM;i40aEkY-9pxtnP1|l zK?@VD9Gw$8+iW1NkXWy^uXv5kivr@p3ld^wGrjl37dGOh@21{(Ybx_%kl847Mq;;K zTO0VY9G$aJb%<2lXhpq~8#4jKig;ov+1n~8f1edakXq7K)XV*bWwA+1#q^|E%Wxgm z*O?Y=0<0Z;8_L^YD!pv`OQjwTNSoXI(!PtS# zSifHK=N}MUpmNoLTx%^nky(W*#99+cU!4fBD+;x^Dq1Lx>q>T~Xfo5>z6s`vTrw-ukz2ULsi9}W*O`7oJSoJ7aP_Y>`e&wGkoI8E|FKZScvi2`UOvT(`@&c zQJWxFqAK=lRbkW4yk!+4En;1oEzi5V z$aRx;=2e)7Hf2G3Vj<$yYMmN$XNMlQXyYU;-H8wD=>biiPU9jW4ehgg`1Jod;;4m9 zD=*t;G^nB}Wxh9dWKw~&>?`%ajnjyeNG84mb*Blj`lH7ytJ@DVpZarK(VB`Q>uS-8 z+w$EIc!R{)F`3a!M;e9~UTAat-p>kZ9hH%)!M5>vDyZE~G<#5^~ zJ^k>p)+3lTyLHB^h;dOfJItbElj5`ge|-4zd|AUv2WX9@zbA3mrsV+7Mi=5Dxr6j1 zQY&$HcFQVlT=4%^gGCRrMSg1`iLZmEhZ@w!qb(ZEmvrWd1C&ufe5ZCdAG8NRyhP(O z_pvgiY%AD~X6!O%wRX~No@Whak%Vv+Ob|(NFJlL)^Fe`u;I2nCk+rBXAs}L}sE*Ir zhHmLRWQFAtBdl9#ca7xA61Pr2~!)nlCpta$5io>))@Ipe4%dZ-&&NUsR|||)y)|2Vyidz3iy(6~!hN~Hmf^%w!?RpHjD9o3 z01XIMHhlkzcQ?LnOJnDxi@esz7G@a4c4BXDwDq7b6+|4$H3j#!v288eycx~V zZvKl=A=#AN4e?`VJ7IfZ1eISJj-1Jdz8&`^@sU7eS<;+F9cgpe4fNB<0gVqwkJQqp zv%%|c-Dl7u3(SZWl+}l4tDsW8g}PI+_TwXK?(7_3YuS>I3r)L}s`)GUdf&8gf)Gqr z>nL9Fpa}?6HjZuwP}O_rlj(T7{;+nT{?7H4QITKpFp6+^UbY25kKe+fh&>U!GCbOpjva|u z4>Yw*pdaMO#Q?HAk>owUmfL_6glv+8i%Y2i6Gs3?Mqw(pu^DHdZYrnLTu8hyc0Sdu zY0~Au4+g51PqgdLDQk_x9j>)CH zSTUWpj0rP7I4oEL3w7#>S+Zls(06&w^jU$KAjsVVur_0xo3}c-M#IjOTcrFY>g|)z zK@IcJv!xaTP$64OIN3oM8}n6~Rihj90q0n03SD)WobZ=A&o>*pB`+(nm9L#%gLuEh zNx8>FJq@dDxb)+8MdHarf)QtUboKtn3koTWJJ~|8hOx(2BhAPp)>mUD;B-!m2YQ5* z(o}E5TQn|N>r9upLkMC#z07KkiI3iOE>e{f#*S+B(rRsQdM=Xg$X2zLdm@I;Jrg}ULV-)gwkEg*5!J9;Ll$w=Z62Ly zcsi-S=95RnnxBP@SO+>A3@&nP>anqNe$Czoa$1cwLj4)R%s@X6iPK=iIMA8QR^;~* zRp{25C^b`SV_;((Aqo9y3%9um6bgL-;QD6COfbFtAPDA)_-t$3GNQ^0gw^~JBqlez zsL>eC3bgxVvv1asa8F413{TNOfgD@xJ}YP!VqYbdxg*_TuU zr7$KNx7O|vv!ADip?XfQbSv`%T439*IVRBRp8mbyOD7Lv7O@x`QBezPp+tJi$S>G_ zxv^CAwO`5joBrG$O=;c(Qy`C$oM|vH4sOx5Qr@MPCD=VsIuHNwGK`nCnI8H~cQifFd5%UWzh7e{K60mLG(S^>*OR!;65XEKKccTgvsZD=AP zej{1HA~!2CnGB%g^th_2kc}zb=73pS8Gm6`wEc*X^RR z8IY8DxA^W4fF(L~N#8pu=(W}((gvS=-%7pPdso%^a#~AyjbFJ|QT+QPcjihgDF`jf z3083dnaN^et!M3GuB=~Mi5h%2>fL<)_6z^@zx?rw{|FY0&qKV)td>rdbwPp4#HjOR z>}rhqr4bURy1^=z+sbkGm70oa4tw6TCAU-iS5%3EC+cneF`=^WeSIDVy@<;QE)xoQ z$oFfE$fnLO91#uf{g|IM6|1}SGK;DNl*Zn%AGO=M~FAqIX-FFTaLVYP4` z>Zv{oB-S#ev$@KWPL(1^^z5QqjoC30N?FH6mXZ+NhiY*L@+-vc)2yjFcFwW(0`>RS zFBf+N3KFWNuy|^52`q0MvZ5j$GzOd}T6VOj8VC0Y663Ru;S5jG%kK4cONyckbeDHs zZ7)dR)>sS52#sQdPsJV%p03P?06a0-&J63XKCVQBh^Smi0-LdTcF);e zUlT5m!**oD-Yg8teD+s304+%@ynU$S!9zc^F8X<%Ph*opVn9j#4;ILhAAMB&AfRIa zt^F&j+m9Uxl_vfJFWaTuxV7jVT1Jm1^H^0=4_n8~au804q^CWxr5?vvou{Ei3wC(m z!>l@kHa%MT#QU)TNhDi}H+9(!h!b&tNt$&`WsbFbwd6a4`xIiPSM4U4@Hh*4x64ak zs;?f_a<@Wr@(38;92$bH8eKmzC+QXX8A!Fj`@B%=pz=3kZwSJY){sNdZeNM2s9vQ6 zuAqeKqE7DU7Ok&@xRSu-2U+d=RduJAt|pDl1Tq21#6eg(g;NO!oh5En2m~{0t%BzV zRp%?#r(F?WJ;i9aZDZb|a|9Z=v?k-CU5B-XwGv>&%Uh^84ClGOq;{@3d|DD*)gid8 zJ>gei>%eA|dntB)r*WR`^Uo1BCsD}NOHvVGv=3`g@Vx$;janckznVE=N`bWWTJd^i zcxP|ygOZc@Fm;D4-4@ndiy0Bl0g=J}4HRP4cdS4|zieqw|FKlX! zc&-%8Hc{&N(d@XO1R&qx$Y=S#OLQ`y(E?I7FJ|IVsH4+W6Hl&OV7j*x&Pvbs$p>a* z?1a=A5Sem#DXa~(h3LmXKU~V4qUZu0EV+OMmaLp`fKtZNs+PDK`vy*11~XPJSHYE zQ1Mo68R`J{;6QXHrL9F=WBbAQDvL)Ke7W3Bq=*ArbiPku=~rfdAUH9hW<2=wcj;qgSxq_%fOru*m$~jFzHLt&w9+H)De8oa8!6En`SKw%0$ZesmnQ=lFXk5Hj1r zISC4;hIUD+0b8B&TbC;uE}$57zx?ALRe`Ec?ak-cX1u>=YQm3a3qwB$8fHjZ0e*{gQ#1lFVeAv@wRcC$lE zDl(plhAR?lYKH1-1&1=3H0OpX%At&6kErX|K@Yj4{Rnn`FmgBCyChR;1p&|I0}N@f zaV4X+x$6YRv~|k3gx47@F$j0Hc}2sbEiQfRJ3v!3*@9}c<(L(pZY4XMQ{&oHEwwwL z`i8zDkLP3VrYmc`!w4<$np=RA@N@zvn+Ic0oD{PaQLWr#YuD?fB*bKBN7UvGZj9T! zaCU$fTd1r<9xE&nH`mb0ZJM}Rhmbaec9X*#N^etAlLV_%c5vX7Jq*5hvl9g;bI{u{ zY%Tc2`EZnv?XMG_i&iyDW5^ z)g_&L(`O(%F}QEf0{|~^_0PbF%fyY5*=NQxS+&NH*(G%Aliad$64WmBP#s-Jo|klz zVvYWej%2SE6?;RT5JIdjP>SiBdYsDHmNmT0%M;5ZaP{V0(8QBci*qkTTvX&~%R7wc z9;j(Lu=)sYN|uTI$0u*s`Z4G8po82BsuEwY7gse;UIwda>JQm&E9?@Z{idyu`hr;F zEN(StgJyeX*R|{XLo&p@71f@g!dY(wD60Ip#^!_aSyx!vxZI84w?4>Gi@+HvxJ#0lNI!NG_mmy1kRSDl6?)p1NvFG4zZEPZP=;oz}h934k>d;t9pZjl|#09{eLY4x8WhB(pfFS=)w2FCA>t?!Jlo zXs>k)0-Kj$X?G($Jn6b3vqm=@-IH^IDDLl6^+X$8gf1WV?}VMWpo0H!d_TYJUI>>R znK(CUcHrZjxzk zTK+dyw)AA0Gox)nY%2@$JjVX4%0IBX+4KA*Bn*r6=1brR5l>LN(KKNd z@F$+%{<_!y{Pm@!a50r&PDJKvUU(f(o(9{23_Q6MGy2&DZEl0&tukbK+QF(#ag<>|7ZN|w;w+trGOFN z+<_c+&i$Fk6uV<7Z}#t~EhPOoc9Lv%OZMwh{i-*Y$i7qGBgE2~IAWmNWMPB7-tH%P zYWL2Q98E{$H^V;~F;yKauzN~Wmu=~sNRB9=&iTSc!&}|0X~ghr=Jr*0FiFi{j>ER@ zq^>Dld3A{w_F0cmKihV_UGCm7c*JEfjrMhUhtUnjqwR#D#EvxWZb6TK^lI85%qhO} z>;z`rF?faQR@{ivzyS85{gBxiJjKB=h9`YWD0$I$H0G&`9+7mGMuQ>Z@|f|0SF1){ z%9$-Q&ER-W?!cI zBzIrO&i0t*4VQX?c0#gWiR|&f&+l!ZU7eVytt0C@3hEs0r?_ zOmZHu@ib=Nl1>bfLD!$i#HOm{K4L;6O(gFPAteA6t z4W_XURO@gZ<6;?{d;m5zjeE2hF_{7+D#0rc?H~yNrXT)@Wi~$7)UF=3wo_Lnia)f% zvC6BN!%T|f_?f@0QuuvJ%hMES??b>uJx6VmRTs;(kTxpl6_<0Y3(Ev!WiqgXfvh4P zYkE;ik@IYYQAlvJ_KP`15#{oN(U@##x62y@3InK@aTw?+K?0|&s<>XoaLOIpvJKS@95Uo!$@@I zo84TFBFGi`BKpw;R-{O$UCf;MZqFp0T9%VM!NVZk(;+n`s?9NaJx-QDn2a*2QQd;e zK}s0TV!)-Y7J8*J<1-1i>0 zWYfK`t8QNxjE+l20XL3s#9bEJ;|^iEnEu;YO>Sw%MpI0z z$W>D}Hg(v=sMpVSZAfZS6)dIeBJeQY_~XCr=U+e16Fd2PMkctx&3Ls?B^bYwKjJHhcqVLS?5bKres-mZA8TBTX9E`L zadP+?JIxZT#iC-9B(kvnShR(JTJb2EUJm)T14dix}3=dlDY}2|t@|d}zk|Or>!nlF$Bx*F0`DvZ#7fJ%93=iq>xL+*mGMrHJj<86JWW zSX^dD(&)+MvDC*etJi1&zHk2w8F+f{R{<(eko68u--6GalQT~< zi9>qRS8A0Bu6zn!%gi{I4Rg*;^%6ZF?;&YkDFzJ0k}Ad+|J{+icQ?Yf$0Ey@FBRkW zbj#=68gM`xjUWy0;U@!$8iqoC$Rk{;I`PZX;W!FNI|iKS9ZyMcBuFyJuw7}p;{EiS zaw0zrGaWn_9_I3ZrcbTjKvdK?MWa7kV$=^t4MPt!txZ)gt(lwUQ^qsFR5oigI+nyKXxV`8lL9e4daO~(X zR>OEMyh~Tbt&j|;HIb};<>S7Tj5{bpZz=)v&CX$T+WK}9vWU~t4RYS0lSbEcP zEpf)yT5L_h_C_0IrJ$F9(yNikl`mgLRPCl?ajkfs1?l_5$9nf9rmh5%-3XMa3WBq2 zXDN`_?~d4i_@|$dB;ON^mLnzlhW_;_k1N4)kOuGO=l3vpnx;*l_e)3#!8t*B7xLbz zRBy>u(xj#9SgFxbuYjDfbtSn;+moq> zmV*@iY}?B!Y^`<+6mV z8kOl$&IEW?3zf?^P1R`#C*VZNmc3bdLYp17gh<3&vA5ray<;g{ZgZE2cKTx|oMx4q zGAP=TqcGI{JzZeb1c;IW@40G6#OF0wB_cjUq-?uRM&#lq_YQdo%e)iTb{CzU$LTxO zH+sXM(Hn{Emh3h+R zjPB3N`PC>X521vZ%;QKef=%eKMWPJOjXR?_BV_~In0TiNjR?dVh?>(!DG!KelzX~{ zz$2M1VoV1uN@ueVRSrH6s?Viekzb>OaCDQN3oYb@>rYr6K4F+e;XpAXC(q}hNNe1T zB8eF1SasP{C)rLXzr{zBxTaI0!+!P-wBlOxyPSR*oK`S;ow8=_Q#al<;<@Z0YJ*>v z;G$#n&321iEzPmM+xNua(UDceTNhQ;``E)wQl5r&*TF}R)z(9cNl&$S0KEskl8q+ihBL*F|PIa*2~gl3&tbjj&SRouz?Pf53J?;PI5Wr zub1)N^u+dI5ExT;^IT?pXXd%M=$H|PyCX0jnJOJ$(6F59t<(<6RWb-M8nioW3?!96 zg7#p?8`zO`)}ft6GqG_|8&gO1DZy?KV*qv0!Hv0Egqyx<*`W#hHGqPQR$vBqWchYh z1)o|=>a|$$tdXs;6;iZ1y64?mNsNFo7*8i3oE&EMP-REQBZPQtlNEd>!qMY~5nlv% zMQuxkXZJJYA*@)*{kDKEV;AewkIGVfj`t}=>k4g$k@Wg;qqSou0==E;YG)Qvg0V^A z1dovCYl)f5NG$1%RM!JK@j71HimJl-Hm#kkL-L=Lb-hlHd~-bo&X({9c2z-%J?(1I zRvTwu9W&`#5i4E~2H@1t*z?wW8w@dm&@{Bh+1D!^rXWJJ6Q?2Oo*BBTAS3=d=^xO!e{5y(P*#V&B? zS9*Cn;n7rrE0L*Jh2sz&%gb6J89vU4$y1e;y)Y=>mu%GyEg-=Y!|22FHJHwTF`3OkV%v8??*4c)G9HI}6v5=wS05RN3500Dv?x4r};oJpJ(?&7LFs|G$1z> zpAx`&jBE!v@R8xc$N)l5buaw9%SR{f_x)Q_(=3+>$o=-<61M)Xw5fz=smpCplhBi9-3L2- zB3O)91?cvEW}meZmPRX#iGWPc&U2xYMQY*}Tl9SEI~0xhvIRboNl`%Ql)uI)@a(RI z(W*&^O)HoLvfLOP+OhT4jN7JL>rsfC#hwDMQ%9ckPDe(m-|n#JL@SWn{iu}DDD6~F zLc8kb>YxscXmIs$F_B1a%aLnNbf0@@HP*AP=uxRRf)Apa@X^DmSPyKGl_Yf`Th)x# z2X(UXBrJORD?{@Mw7o<1;5WZd@RvL@jyXAuXZD?Hi&zU!Rlk|iU}delzR)C|NiCr^OI zs2ePFYNprT%839I>#cSn(>59IRJp6#@4t7M$0Lj4@P(uRAJrqQ!S`>StuMC3&Ben zJa&p?EVqO9OKzS(tSc||lCRA|-7gA>v@tM#1L^pfnE#l8JO z@3 z09ZymE8f?tDmJO#JA^jP>(TTFsPp=9ELe7GpN(qOx=`bVBEJy3ZFLQ}>Q#6=D`F3d z)zr5URzYI+Ji@0FRAZ1$REik}K8-BrXylq>4lcW!&s8CEeGa#|gk|XQ^QQyQdqtE( z$xjdN&cO0D$hI{o`r)QdHyIKR5kH^Pf_>JcX3g);%99jP#0=w4oHK!h9Y2U%gs1`4aWVRf*h@Pp{%>7D8rXc4At%b+%`_ zceQ_0KYH~oojNeoXvBr9WiQ0gYJ#{_=&dl17WH8uFjs1PUXTXZfYdSFXbYd^wO07M zVisDDwh80{5K1X$wRc!qd@G8z@av6#tgm17`O5vNcmu1vhTI!vl0QYP$C41D&)+D} zTj)d*JQ6Sr0!UfFa3tR*R){98cRzx}W4{R6jP_p`b8s2L2tha8)a7nwQLOz#j|?=$q%-l0)fJ2vA%ygSAy(Ev>0 z3ny64ufJwZ>xoX2?np-KnH|}st>CUpVTj1Yd&Pm`X;4Qr(_A?|^&NDDgOZ9X+rtaF z#M;(h6eBjCNBSlqj(wZBdFYWqW`n*p1l+wCZ>3MAE*VS5v#B{mZQ08U4g@4XtjEWW z-V`EwCOJ?>?_2U{kK4kA!>`_s3itQOrXhM^-lT)EOQlYojd85EmwP z5d~2~Ms$Eqk6+;$ui7E9Rz&XuSQsSA6b#~i&F6OZNUe=vL#x#D9l*rJd5YdT9y+c= zrkpY1lK7<=Lgk<#jTTW@fc{@G%;oJAtG-(!D#?DU5la{jHhd z9^#mg5PQ)REsIR|3pN_;t}bq8XDdq zTdb~L7lUPFTRh`Lg6)n0%i!KvixGI=T+1x$0Cu^=LB@eD14QJWQ9>USJscq&YgC;8 zqJK*V{<`WS#)#d3<+(p8HaMOuAcKWFC?w}0L|cK{TRn(`=+W?ERHw?t9U0w>4@I?! zx1t%GtYmD%X^fd1*L#ib2b#wwJbXSG3ndSOy0)r{$|oZfp@E263Ki5s!CP+$&Q}}L zS3d`1gLFtOxH@P`I~Fh_zCxB@Rz#E%OUhXZ?4X~@4q#2qo&`ap-aJ&uiE3!2c-yLP zhQ+I}-I89)`Fct=Ug}#k_F1r3Gri6-PmxN!vX3Bf@i+^y$-w*LPd~yx7L4F>eiv-< zkIRF;I+Zt9*DdeddF)*uO0}0K7@Cta#f#IzbI43}nc(^DoXydjAf6ewkO!DHf#5h4 zL?ZStb0bzX}H^rWhhtg2V^AT111g_CAH z-d$dNLhJ)jwc@KVIV(9IDN-gPeS!-XQ@M`3kSgp(MYpMk)t9J6k;zxMnSLrN6P{t~n^w)GSY^A(yx z!LB4Afxwa)zdG>DN09?ylfZi8=ifdkJE}u6 zoho@oJT6NpODR*{L7g%TV5#=Tig?6*2CyYf3_Qz;WJhIHKDNX_#M)|&@2Jd$jIdmm*Qt{ZT=yZG=TvL_vOIsm|WhU0%K z*BQ(kM3+2BDn=EGBR+H)H@lIF;lW(|vpnAT8%;Dat)V0ysB2purLNb@#X6ZZ;c~~3 z4>Kt{tDEUdGb))mY3+xSm0n?SFr;ODb*#4dMn}O(zf`?njk?aOR`yy&IizLwD03Jf zx)<{s7VcaV<%N<{bHX!Rk>DB^=**Hy7KMLh?~9DP7e{(%I!B%@vxDWb5fjMN#JhH~ zEgU#scan}$x1ObywMWR@&i?_%WMHXRnc$PL$yLjnd*>M=vgM(;MS88h7M>qqdOUsY z*d2%{dO}?p0b>Ept4%rtAnzM}`WIVFi}Un4@1$S1o;XCi6pz9yE~vh6N7GKQPKYh9 z%U7Nh^7_B+&cuQOs~d>^tX47`pb)dzR3=}Ye}SFi%A7~>8!R{BtF4N<7ebCl9CDH) zQM%keNaVU0Q6d1v2}bUZj?vKu(fB=1A%_D=`sTLHGa)Trfoc|dn$;XJ3XyYL9VU|` z4Eo{AP+zy?iyX57qkYdz~$!fk-8<;gO7&4!YzWs7#KN%Hx@)VPYuCV*lU~301 z`&)D7eadDh+05uN^BRhwcgxwP%bo*P&Ba=)4K*G&<62Y4i9~9umUl@C&j8pUUS`DJ zTWW13#q9S}wV}6{$ET9FKmp)>qtuh8F+kP-Jk2oV>fFUo*?sKX#BmBkeF>M}XtnAJf^fe3BXlz_i_J#+8&Gzz{l4cA;{1r5+$In-bPvBz**(TBt+AZKPNK_n|12~H0( zU;O^}U+a@V#+wSE<+}^XIZj1x8wdk?_?e^~=zMCKNNL}8aK*#xEgPBiO&O{ryfz#% zvh@WYN|tN)NbHPv^K;3p*ucAEM?B>rtA#zel~2=cwy|&VA$6tgIs=xR!VY5};;e8T zu06RLv>yYCSwDI7oIkxP&KL`!d&}azs;gEk4JO^aWL_GhsbuC)$x-{XIhH; z7u;Gi+d>2>a+tO&;~qAtX{%Q;jj2G>)_*eYMu3%9EP)iNx{j{J&W-jzaG7JwtZp|B{DrH zaG#Z7F`=G^s^>WqdTGW&WRBT~^T2DN(#$&v$7yhhgiHWCHli!`+`*I9p5DeIu)q2F z|K87wwK5m}B)Z=(_M1^?EJ#|wTo-j5RjXFy+RdXX*0Xr(<%x!uqA)8XtJbpR5NlOt zZdw3PtmXE8J6yKL>PFf4-osZ!M8!tOD!N+Ilgik=`cGuU=U44N|M8Fb6e6EUM88_2 zWF$tmP_Y$6A$QPR;WHJYm(Phr+Nd9wnkG>BJO@NH$p(01+s9EKzs~zgb15f_L@?W4 zhh7^`6I!40p5_9ulvxi$qNc%S?$k;3bo_?1N#OO}^;jA}>bfe$T8q^4lQX-l>%`#R zxgtAyXOzFSJkhBf?WLs=-Vy0%pYy&8rpnbi2Jics0>k<)qmm!W4`b2c7oKEm6ewWfBcPocd8ILit;*Q0*Eid zHm7@w2_&3H8r&L}efOfplqAPm^=sG13VUJ5sNwwi3|;Go!Zn)WGmG#O;wpKt)f9^B z$#m1hEh1r8abkv0M)iz}zW4cMU@dDh!U7rA{nFJjmKnvF7KW+Ou!!1-n>jZQjK|)k z6JC{P8kWw#9VmJ{!v2q8dcu3%Hb4oHgx=5Bkx_Sg(6wpg52JBF{1^(@aTZTWI_`W; z)zvdun{e+8J(;E6b(HW3Aa)LC#O!PAnGp4fpj-^iu+@E}%ahHfnR3V2$EmGJD0J#{ zxfj=9MsR!Fvoq|n5@cAOL)@&m4nw}4>&`LM*EgmZE=a(T(>`~)D>wbPbW}~;SIujf zeoDPmbVQ*yK(_1GM9OeR69+|BrFQpiwC|K-o}XhsF6Rz1JNoOzgPHl#a*4oJ zs0p?#;qH=?9d~{2>vl1%c<*>Yy6R025^CXGWLi)~Qj5Cu`*K+Da_X~Vzc^p(7L4jd zE{&!s(EAup@-|Le$jhol;ezvxv@Rq$dZ1-Xx=eZY8@GBaM;hr6oeIO7!km zp~=am1s5#IM3?B^@}aILcJD@+!;H0}TFfn^=)dAsyIX4O0QA zg%4lFkFTZ{2zn@R+uZ-L5M0kTc?nb_$TJgf4w;5GM2pNpBaj}X_1H#F6HV)InYmY6 z*F6t3F-TGhIP|NHZEFqpI6D)2isN;j5WRb0A{38na1e=KZ=6@0;@J8PbCn z@2m5X-CJ1{@T@CzLm4FbkQapj(?_`~UlAo|JKuaB4HF14b#`0yUstFy^8hK}kIE_y zN0A(q1a>C$46}!;nvh5B$am+&m6a=O3j2pJ`FuQJ(h-^I9-fTrhR$>k31D*7;Ii_OC}7Y4Psy+oaqucZA*+92A%0&D z*^-Mxe^na87dhW z>xrmfGSY=HQ@DJcxpU5J;0#|D%6n(N(R1}rT;GX$j)7Vqxc6atnf}@+B?rYTB#@{tx)6ImTmBq7N$|&MxOu33T$=kT-R+A-Ce7zi{&-9My5d%P8FO~ zpOM|zo4E(b?#kehzj8%@VJ-|;=3`n(R7qnh@U25=0~=V+^E?kgS4js@nb_#=%`7En zyRFa4Rkeza&y-6eB}QqgD@G`i5#)=;ip;7-)B&1K3#L!$~S`M>;WORNfI2_ zjl_sgEkzC#kptCU({MpyE4dP zZrjSWY9&Q3@B0FH-){yV)QTXL3W4UNuqpxU-jrM=qTlT(7pq|-D3tDwwJK|MMaFW( zGBQz7UoLH`vPv!xR@55kfYD~ZVtqaz#Pe6Y{~mwV`!B5kvZDj}WJey{N1W!zRwX6V z1dv)&Ui8^?&@{aRHN!xSZ&scNZZ(|zW$W-ujCoX{nFub2+w6LX*X2IbT)@4Ju+lM} zBd#tOqMmw~(NU4Pg5K;FIG0y-Rzx;}h&x%(u;0iQ2-j-|bR5xar4t_zr0zwYT51q| zsl*PsUMJ~v_k_gIM=t3`6sa?!d=xK6SlX?8GKa*LD#no%BBS5%Ks>rLmx5%ujV$qd z)5>j5w{&TI->kG)1+gM$9i$)JdeFTui8tig(=HiGH$BE{3ri(;2X-H6-z>yp{eVBW z@>$b&4FyV8wx*yjCssbZyb7_4r%Kb*qg*G5ev}2R968_Le#qBUv1^)+ls+YcmP>uw zJ=!DX7xM8QCsdE#szD^xyz+Xg@?d_cGGL3Sj+$<;*6O<=qll?Px#r>7yDiq8AIzD( zN0%Eo#!K44BuyILR(oU|v;2v?vuzzYu^ATijRutS999-sLZM=5c_;R` zY&JXtMI2su0)C%qykG4+UDtH9UxePee*9v_`% z<)3a$LwR+t`1x#CM3wOlb>-w<1>}t^U6v5T^C0%IuX3EUj2)QP?$~eat(t0_!)>pj z=)eamKa-KLidP0bT;7&+aC)uCf^*TLKE*yQd>XKX&|Pp#`$5l?s^1iH3 zS&=Aey;iMHR%Nc0j1H377MYz%WE41Rs#*|+UTl)??stQNrP64tWg+8Vox24ekwIIh zUK2QGykk_7QnIylr!_Yq3`H^kd69>JB%2hyckHfAwEog8R0Vp^+=z*pWCBtgm$_6g z)bb2+bvVpI^WYZOv*h!SLgNj@@86&2Z>l=eh^K&-5~XX5KmVzoFT2qZ=7o0xcJR~3 z(v3fRPzkZS8j%HV11No|IEl|1)YS>F9&gNc`nhV`0cM5*hoVld(ECc4IF5xhQbkgk zh0B$v0Q$aEDRAPPjt0vXD2L!HO#|e#>t&}(I;w>1zEvlfe$AU7kdd{ppbOK%zv)oy z`Iwm=ou@&G4z&?)DMb(1OL0Tip;5#^ueSo=!-9WHA~!7CAPMi`8IQ>vph@ z#!s63ppaDway$nK^veV9&6D^EXA_MrhmxV~$S(Z!Y<>S(7F^Eu>+K5lVY z)56zPOntn-MSTD^+VhvBIF%>8a6p%O`0J-&%uaapL4VsZe2t~o$&;)Zg4>Hl-5MHc zz;CpYz_PxmKmY8A{l0IbKrOVmt$d*rIY*gGL_E*K@pU79eSYyu$twA$SueL)!`-`*;1@ z^UeMzD990WbqEFFfRH+gwHcANcr?>*){o+25an!I7_Xhim^+u!3N!=D(xQ^x6P z13l{NNs=STM32@%Y}9hHsKW+Nw1yqvIn7T^B5T$Y&+{X!mCs>`F-Ci*D(SX)2_+{B z*V;_tb!zdEYI|FFjP%WB?>&m&qn>SBaxNPduNzMXz;r#C&5>GAq|;SCR;P994fCU5=WoI5us(K1sHndB_u|>-81zdpLRto1!&IRuU|)_?-Mm--M(1U zc^QbTl=Ua+@$^G*F}ud46b7nC*hmu|!#R+)BI13-vX#1UqK~^hrkMvoGAQXA1<}>w zC9Ws+^mWtJRUo(E2tA~zI_h`ojVo%7fnEO9fBZQUfAJ~j3R|s%zeos6tXa% z+XdFroOh){3ax*n*q+zc-qY+dlbHu0@XCB$X8`r(mSrwhd%Exi$* z*54&+c^f?Zms!rkQZ-zXx7VL-Asz?6%}I|;>Sfdzm!L>}>B(JZpsgb~d%AWIz)6Q! zmod+to}YtT$!z(GJC4vjER=&!)<$||q8~bK+hvrDCe)#To|NuV;{_fx9icdJ^N8m9 z2*$s(a=$XrFO`#IDsah~k}kcYfn?fb2-(a=hTxMW%{7=9&SJVFJIP>KM5(MqW<&b8 zNe|>N4mST;!)@&?AbNKT@k0`67UO~S$TPCA3<4k?Ig!h$#p7iPkm0+qJg+k@;e|)l7xiXGoF6^1yP@3W`V*B`;S!SL9!>l}%NHvg=m1aPV zK0Y&7)&tIE+Yphq&|0XTq<2xO{3WkHUQbdvDD6pbnGcP&_$1)ybcW6#k}N+EZ{O6D zxDXD?c}_F*SSqc|&%^91o0KeV!nl<#q2u^GtlCoAB`2~Xf*Xd(Pk7;S#9ctCoThYG zj_MX-MtjJC!XG4whKq>q*ob#b4yhDed(Td;9h;wA^Q9ZQoE+7i!z)jQ%295=ZN9Wy zz7?kq)5lbkp`nAm&uYM13uvyajp-XU@SpuEPm3A=gXs8Pdb<)@%aH?69B5kCWKm_B zC4=Et=J<1B{2|CfkG>6Or+R0~FY{*ftxb2(F1(xyPf$C|mJ3sMDV4eXjS~lXzko_U z-BE~TFH@P1t!KEp5qk(s2?c8x-k$RwyHD+M>i`iy60`i9s8&i`B$CQ(y7n)f2-@SR!ZTiwU)-h zVs2F+c5f5GN&-VDJx?;^y#Wi(ATB3=n4N`{T^rf|vH$#y9s5u3(l|O(=8IU~Ad4K6 z6Ld7Aapm9IHb#F0r|-R|vB~ujGj@R6oMCT|Dp~;S1G~*USu9bbZ>hn8g>u7vq^of%JJ z;t*v+FiLQ2AvsnTFL`=?P)^O4xSr*mOI@@`(`d9;+7@O%C-~-|t;)cNjhld2F^-R+ z=?sPk0|T8JC#pDJYNayb5qc2U8K>OgU+j2&KAXST-#dL0aoo}xbw zta~(pNRG)bqwF2|d;Ee-b14HUFDl2-69SHF@;{&F?Of}H;O3T=(*Tq*>KZY)Lxvsjsnk%nC?!dw zoV_Ej<+!RcpEDG&*vttDh~8d)gw#?KvVF1}`tWRr1ZIU4YQ^$?8+`>z&rH@6v`Ok- za}uo>dir){f}4mD!SJCx;>0Fgk%w7ZZObVN^>Y7GRy0RZ;Ca%wvWJKkZ*U1J#Aw!;x4rc~V zA9CiUWoz~;P%OXxI82sOxQ|>36Iio z-PQp8bv#jk9Rx!HFAD}!BiRiauzvxW`U1!K$3smH) zI$TVVaC&1SuW;w+T2*BwYsuv4cvf^b^Y5q&;@49vqvCOw!YwN`7TM?qqIYF`+zB`s zwo>CsyUCgAi|%Z&h2HyZY-+tzbyjpHithk9;eu3C4!UT4Ofkr_{U1REUT=Rr3=2ds~aywVdiKG|Z*{u0S0J1R|S9v#%B z0d~!#rwzF1e`B+}^6@Go7P4qu@%;PZC~V;Um#Pqs97K%t%jX4@^MtpiWxJ!1!dXZe zs7eK!-uP~%kUN7eR^`1iVsWep5Jt&t93sm-u(0ez5)l0dmoU-{WN{-cVa-{~ z!+DH|RqTtH$?E%zm2_(M)DDnHL0@Q1nMt${iLM;ouH|YiVi1h_(7@ux8Z-2$XSy75 z_!rlNl}tD*+47lCC2#f(959zMlmir|`xKo8>AED62`-aajVoND(d`T*DwI%+#nGbFZ#dtiuDV~&yxjUze|es(UwYwotk7tJ*&GD z@e)>nOeFTZyR)<3Ery0k5F&SGZin2h#r(tAl6qEPZ@k>U(*M`p8NFnLL9WbCI?l3& z!$Y@4##$NK{eS)Le|sYEMLdX%+NcC7+iqCB)D|M*?YksZHMsXc`34gT|RK;>!zI~mCKrwOe8XGzbSEm`! zyvb`aPcnb7$B|9oi~r#^zK-dYZ0yZlixpep^M?UX`h3c`n8^&=t@HN8@stQM;gCOL zhcwU!Pqy{rn&@Nc8tfLj^=il?O4&%vR#6#C_L1i7A=6wX@V)JcE6W&2xEmhc_=6zw zG9QPBJ1e!R=H^fu8EWQSRgCs*#GMl{A@-FQS0844n4hd)ztPs?*i0__<`=Vxk#}I2 zM8obPwrzWqw!Ku+gPkWGP2Rf73ChOg2^sYqmu^h_t$=d5x!+qA-hU?mUq@{ zTH3Z|LvtH$089R`fJ)yU411|$oh`Agqu8&`qI!<1d16W?+@p-tPCpz%;Pq96JBnp+ zEtMLfLh!9ZAggj^2W9tJRlU1+KeV$)#Or(+bI^Fa6;?$oce5QS1%|UyHg;v!vxIVV zh1~B(zd6CKMzN!Xz-jD6BpSVW=rOFgH@Y`^S1x)^0}?M3QjTK>bcBx70;E ztiqR?l$KFoT%+aszH-K3z2kL3qsMNCLeKRR59$`Osf4sfx-zMaVZF4;O-Zb%WiTv=Q=KR`hl!I3b{OUK8?(u04rr!BlDJThw~_bS(AOuOki+p4uM1~T6w4LK9dQil zMyP4*FY<%~r@-s;ekub)WglDCc9yTHEvb^!$Oy+-ok75}a<&11E)5hT_xOA9#F6Sv zxxmbrT)_^*i5=K)w6Z2OidwBxhFSKLGq9eRvE@Z9q|82po`cs7yqY^?pIMr!Mms9| ztWsU3r%}5*Dg&?LxGGTGvgMKm60y zEM0O?x|cI@)tQ}-aj|Mj8BhIz{&H6^$t0?oD4Rik-`e^qT zv$~ssN4$``-&MJujLPng=*GJdRfWoYYBythg~jUKK<~!hoH6z8&Z5so;e9(Y*NX1_ z?u_W&8BuFBu%e#lsboo1V5Te@4OB^k+xz`?=I7u4&cAcV{xdwzD57a6qsZ9_rw%Dc zR!meJC$gHuvm@n-6?`CLq;Up|>6YccpwfX84eoq41JyVSJ0|c(aCPISPIYg43%cl2 z&0}>T#fNYWi)?9>RA1koX^#1V1fw&6=L%w>6Y<)Ji<25EOG5DaCeZ}E-RO4aZ-Hs# zhJ1_YH+DyVFGj$QG%Anezj1Axbj~S-Z73BQV6#1iz|QjSafU23*M!)6L=KbZ+#j}> zzPrzII}q>oIKNmtNh$KIlkJ37-@yMu00i)k=wc#jiu-?R8Uqqedwaili}jTHCm*GK zO+;obFhbInoQc`-U#D=LJan|2xG1fmdM8se+k$U1F5eTXbd~;?GpzRYuNsrM*{-{x(E?xqEU3SK^5nKTWQD)WE7TW z9Qu5<3wpdtH7TdcVSa(w>cjobM+J;Si#^wU;+X#VmDVAH5{1~~_;o@aE12CFp9>d> zc7$alwtf=nJQ?;mPG6!xGh@G(TCEOo)VE{NxvFK?u^vg4vkB4BwHU{j=u>a)>F>g1mj zXu}Ncs^C7>8yTrvS0N4M(!0D#(v6TeDwphlIkN+e|7tcNHe$r>n%1-7JB5xDKGi85 zoMO^4>hPjCE~%Wx>8CHIfYgu_bYS_cSPNJ7qT**TpwlrD_Rlb_^!~XfpcLrnH*(J& zF>zV72V2;gP|ILkcM5c02bFiCKqe1_bSuf%XvZ*Ub|ks`B@X!>I?OcL5#o*9#4e@6 z_ST8rjTd_pZV}6ms}6_Ma20tCt+p!Uf}F$PY;>QDF6#XeA)g}fbl>smxyWv z;;T0UcNzyK5MWXHY19Euy=5Hwp}?hsTVTg557}p_E~(6w`8)?9B`Qi&4#E7&(5Gh!Cxe zdSWk*Jn%Afp^Z~ENsq)V_)qkYoj}BsRWW9Vz%i-f=*^F4Z9IDW>JB5Lo~)&MVxE^i zA~&^shlOYgPW9n1cPH=H!-;K={t@p>x`X!19I|#C*5Zzcm6dRFo5bjE;>wU&*d7wS zefik9hg?8{)7F*8-;zC{%X-XI*$&o78Qyt}wi%m#o3qm4;x{6Kq3tCzJl@u5>B*>t zu^WYJS&oMm$;^r!5c6=jpewLQmdz4R+sV%G?)_~!l} ze>MugKG}^I*bPK6{_5TDyU9$#BHA8EGzgL19+x1|Se2`CMJGZ~XmkgbTnW)UeAWtN zZz@$`N(L4a)fF&Dv&sN`((=E_YhA1>*v8WL0-*MvfBcT$zn{qH+{lbB7D*Y4YnRv* zh%P;!xr14Vhtrl)45sh(by6I1-1sDl$!j1Wkb{t+SnOYQbYkRf5>6&%1$A6b8+O)< z*oyL&OPVEiv5G3i6^-XRO`gnyfw=U9HCow@(Jn>h%FHGDMVN;?S=lNY=Oyn9EA+Un zT5uW-WCX}3@`JKVzaGSjcIH{R+;InPi<&DcZEi17&rAx*yQn^3YR=#X!;!x@dO%@btz>#D=18OrwOi%H1awW-5Pfv}9V_W41ZOxE?zWk`sCAT$I<9$WG=s z3LvzyE4lV4n^rV;bMRzEK9y?;;yS&0I$M*MKF{j#?5Qd)Rb^z`=k>~WAT^d@)UiXh z(9&?WBe+cDm(DB9)`nCH^gQ{UYeJ4an?8w>BZeRHWt}`~RF1=fPHe)f7yoNZS-5$B ze_kHGw9(E#NvBsb?l2;Nx@LvJ(IRJ0882tz-0Tg@m@VQyn6VD=*fV8e4exk*rV;7> zDsmzxC?>?U&oCZ;(9z>BWhSTO5-osUR3aC!U2wyBX;6n&#hC`}=aEQ-+46fZ0?^7t zOqBON0!i%9s0cIas;JexS=#dy+CsOC)AIYjc6e-)YkFt5ha>$X{qb2!;`u@SA7O|Y zBK$cxVR69#GTYSZVb}{=sy#*ZTa=m<-H9Wt8*;(uL;d%}s`zEQP46Hf1bO0ZyDs_X zkS`RImf?JvY0e2?kHk|g$nN7McBD`*22(AIWLE|GyHjw^ytb`Y=$tkOY?a}dXkDrl z=Vv2-jQqxuW=e5-aLoSsL84fqKUo>QJF7B^Y2%aq-WymHVa-p&j~%&y*D zMax+zdD4~vzE1U&FFnn$V=@rI!$56bNu!>LiioP}ZW8pgy4u^+6>hXK5v{%Vq&sD1 zqjOVg(2KudXBCI5HR-%}o@=&)DGSk?V0=k7 zDn{jsLUhM6rYJ8^kY@Z9oPZ`{RMJ+}%buhV0tA-YydDd&s9G2bCe;h?mLwpYjkFQb z*vO|CWS1)scahKQ01WrmYuFOpC*FA6y}KxrJ1Y?JDubk+2;|<*$So(}mW;e~_V#JW zV|{|v%&4?569HzNQ+v=cJhOH3c2UDNC#+h3m z>k^)UAC~?j1uec(;YDS2>{QZ{G6L4Q#>)5+c=jn`X>{B2N8Om-(ER?&4;p zGoG+?X818YUylk!mM8ushHk@K8j=EPojCg#W9?+O+H+$nBgol;cF2!8szbHLBx1&(+^&>AN8F@O_ic zH^@dY({X8gNTzrKQQ38f?(%Et@q+VwyyX463j?&4Py(^<>$@=Gi{`KEJ&j8Q3280JRmI+OeR1^e2 z)bu(?7(J<~L-QVP2LjtOi-@zjztcd={)KjFmg|buSXvP;?j+iyWm2$~rsKfPA5Qfb zdMN}fK!#TVR$y<3^0S{6<1o4E+#GZg-P^Qlt-q~F%?$be6IUxK$Y z2oH&Pm5P_aXoKF-I}cJ`55IO(+f^Mu>QyczQ}n?$^lKD?aW(eo%ZpK8eN02SmYmPb zSL5!KbLt^r5FEv5z36ybdB0QXpi@4^>qv{qMy?zN#C%0?xcc+tJ@AFSB$O{zX_*#= zY5TtNFj#?qxiu+B&QjmL809$rH_25?msd;`e(zM}9654JzTBKZRUE9qoG=_!abLPt z(gAeAcIlP1=Xs=91@3%b_2aB|6Qrr0_6pAUN)i(r^h)|HlIA{P*eW%?A?9F zDD!?{pYRfWTthAPg13yerb@2qs~@eYdny%wjS1J}I^$WYuYPD>HS%#dc3dBYfnMjL;K2NVO*St$e89o2(4vw#yWSeMu#E%a#h zx_8J;n8RxNS?tZ#Wy(3lZ6P<%lQ$Ew0Xt>X?eOWcF8Ksi>>SY1Cju#k zTkfITSaJEK(^c5LQ#GOm$$g=rD7e|PR1fq{EbUH@E3oysoI`U7&J)c{FAw!iX}TLS5RPQ!XG-jmAR zQzm8SabSLc6@3`nQ07Y}{)2yh?0VvNds|g(73x@5E7R;Ra%8FR%j^b{9w2=N6!}{%t22aW93?uF=tWB&Pb$Mgk(pR^x2ukU~ezH+NROLJaB;6q!G#qHyaIS zP4>>f@4x?%pm1?nuzu!ou8Np8me`gs4gbKP0v56o_xC2R3}Ly8^cxpsWHuWqp!S5# z9$&}Ip130g0+o^#g&WbjC@dYZqguk_P(IcbTc3j+m-CGzx)Y~Fbgt1&^qxKIT+E`a zZ0Ea#wj9K!4=YQu*d?i6X4b+~9+M5zj=`828+p0^#X@&~XeTh%Rw!mOazPKoiax=TR@T`0X6s#Eae^0EKL`6}XP~v^gJ?> zx9!3qjHpb=P3wC#grl?8BT|+u%KJGwDEpl#;w4fdjt7PT<~_3m2e=)o>pP zC>i9SOb#4XUAB$4IFO{hISiR3z!-F*Gt! z)|`|O$K3Asjrm6WmMxhxe}AGBc?iuN-0qi@>d4WAdaH_^3&8uQzlZ|fKOMryq5@Uu z#-?g_Lcnv>Ht0R(&ym$eH~kV(YbDIRb<}xzT!;owQ*_iSKjm8XQcZ@cSnRbK*T3i8y;-`z#q6C{SgM}`1G-M z&l-U{j0#|15n1bEr`eCUahS>6!Irr85gE_2Jk@xm!>!pDa;cAgJEmm6Y`S@%dNB<) zE<=iqaL1PpdCqY-cIicjKb-EUmoT~<)KSlT377tWLX>HabT8L5U$mS6BjhE40?1f%Ff84*Cgg% zI8SimAbdv+mS?{$p^3XS>OAQ!SBSUH*jyRhGQ|}b<0*!Z+U4APtAZ!`&5izDNC#-u9o)XH%OIO|6T2%|dqYHt!c1oMq2N6mt`MYf(JU8YMwPlF@Fn zL?N2Mt*5T1Wpa|@7{8tUX6+%)(0q7BF}BZ_bNlz`Azh0U1MoHS1u0zcW<(}JNEPU^Zb79 z1q9C{9MoQ(2v+VgsFoGeXgm)lOHAMjXGiI#$O(j*W^BZQOfMmT#9Z5m_-4)2acJASI>Kn(gK@CuZL z695eM-u!G}P2eGd<^da?X4LqNv02W^7!@1(0s3l|MA|F?%;q~0FSn#kmDrve{*2RT$Mi zLwBb~%lyXt8?RmBblT!4dcU-^dr@8N%y-Gypcm4euy1s=0d!v|P}Z?LBe+^W9^JGd zvm;i;dT4yQ5-j+KZ$~5exqUlR;UD&*%o2|?N6uOKK0N*T{EzScX61U4$rr@MP)wc) zbXTsm_yi>aJG;@{FBnLJcfa=@yu?A!CmS4dW+_F9f}k{ZJ0coXLWF)mI*$|`|L%5O zj;LaXN`SohAjRC^{ml<5f8qPrio`ea(^0(?_LX&Xb*HdPh3opV5r;34L_i%k`#4K8 z52)`a^JAR$5zXt+bgar)7`j1_m;Quwn;C^1X^;}eI{MYDuoA9aST6I#k6b+Yb4@Gm zbu>@6$=Z!{z?iyj3{k&GL{IGZ08yntVM5HLM1>KFIMEcWGM5R)N(1g3{+3|5w%?;L zCXlm=lsd!8cYAq;JhawKrCn5UK2-)dPkzBgB!)Q@Gfon9rZlJ>31o2Et`XXO?M_d2(s|LWFmlho@Gc_8Q;Q_Ps8KNR*tX4QfZ2(!>WqkB${PJ0|t}(GY0%#13@*Qkhkf)IW~Y(9|~LIrJ}S&l4Tu5lEv%CE&A=X^elPTXlON z5?j0A3ZP<@LT^^A)$^@T7@Yyss!vw73al)x4YAP6fKRoHc|7!SInNk-9@@Z_ve%mnd@_S7{)A7U&p^)NQkmqU0B_OR>~1P zB+*eo6+8}!-m$kzfY%y-<*hv$7IvzgTb|!<9C^p)43DLmsm=rs#Lj-pDqQV>(KD0N zk5#Wnv$NdbGe@6*z4+xQ#r^ewtto_v{KU#1!F@3`%0uhF2o@9k=oNGbY;X zP%&c4Xm0QJbqzA6@N_&7bONXv;Ad_vBR4&^`BNxpG_IDN@{ zHk2~{Xm{7Mr{xqg2`3YR`wO92R&?yhiuEh-(1aFv4*N4Rc1rA^Sf+_|at_lrWJOQ@ zj;_E$&$O6Y$EWHK`t#ra`6qupwHEM3zPdFU{qABKFMSWaH@cZo3c|3Ypk1{<0g{?n zL`1(|w!0+nTgyPgCio9`AgYnkQ6xom_tK?7cA+eM8ymq~Xo`oClqq!vVn?F$(}=xS z{Q1Z4-}TQO`(2UQpTH6$kTj;r^39PDBV(Tuc5h^d|8O<&DA_EvVfbTr6I+fI-2$D6 zPuAWI6IuCk{1J@2>My#HOcp8UQl^lubOGmMoPPMATPJO(J90GLi7@PBG4#8r^$WmAKF&RD0z-m^)@m?Gw_v8Rv5 zW8~~PKUf@Z5^awswCk%WOAR(crt$fieHbINvLpaV+w!$*oI9B!Z3tOuFFLNg`cZ& z+?-%r#{+V3PxIZJW5NzSxJz1e}O>5LkZ(ViD@e!^LLMXkiMEQbM^X(~$)m!r+try4!KaIKMPDtIU7Vk;Gat%Y!DMJtW^ zt0p9`c8mD5shXnmv$C$4DQD~4Mz)ND!k{2y72zX^hzZ0#36s~_Owbqec70Isdnk+4#i7N$c&?umgWIK$vsxQN*kQ#wbM%n6K zd?F(2jh1gIXT{1YY}+ovpSof|dAYr!*3PP&imA#2y>r`oM1dG<4^zglb6teo*Pql|M$>~A!amT@UVc&mGCyW$qaz+!dD+DYHc-e=9b+L@?@7yKKkyzkOW3bF7AB4F7CKVba~+{6 z`B?2kU)AcX4c&M=lnJRXSl$*Mj#~@%1fn=Y;}^T!fb9s$T*Pv#C-&wPwfKd0|5)#L zU~g?>^p`DZWluzN>n*6IN_RW^j^phrPq+PJ$L{B^|Mm9bh!P#o@2u*Ll4)5R!}tAW zJ%QN$rp9}f$p~Ps=72YLG-a^GCoCM&Ftva=*-@D zK3PauM$SJ?cDp=D1>abx{`dUP|MT}Jq!8F?ZB=o*>rGW0gQ-S-$Sv3`mrjKfP^*I; zu_c!>3n1fJAQ@Xf+N;D)&Uec>dFu(mmgo=Oi9VBQK62Iu*_nVV*m?31(x7OMlQWLi zns;1QU>i4+4(zF*Tcr)$n*V*ramCz`(H0t;l+k_M;t z5N(eCh3_)Y1MW9^P)v$vGRDHnz`9L$>8>~p1R1Jr;Lo3sSa^KJ^R4j?8e6LNvKCUQ zI}*SK9as*RQn#t2_1)21<8{Zy%rl1QT$QhPP}F7%iczt6|86}?+nM%f3{k3FC#9~g zHREuu%d`RNm1Y>#9G zDw#u*wleO?5F#3_hDw!=gw=Pnd8L}q$E>Dy*+cZrdFvr)eagm(Skg9?>i;O`m`dw@ z#n_4wGn0pw?|?wlhx{*%lwkoB=fLZ2teIU7tgoRbfy48xYaf{uOd)@=PKDn87D zo)YVvi8?C^KQqy1xKa-o2lpKaKjT)*AId=G4_Jo#(xR^L6a_GoK>uv;W_K4R^GK1Z|kpk*xPbX8?!Z!q%+ zZ9ZOXbar<|zTHvy{I${=cW_E{N0Xkpa-2=6ME_>_nn6k_2%6*YHzKm0oC88^VteTOsFBo6=4@Qh z$>jJ)s=_oc%f2b`2Q3=2{L2opWz1UO-O_QXecDqa>&h`^H1A_mi)T(bw%9&?2pwRn9060#c^#oa zPVQnN7;vvRpS_FU)P^kOZUjZXNfVVhsg;KR&6ultpQ>7tYXF;C9b^U`JBO)xYv{Mm zd~vLg)K6k!8(KPc&8SRHt2$9-69d2!vqPtf|IEWzCt>!Ql0(h;Pqe3f zF$d0y+?l)U`RAW!kt~?=iA!?PdwXb2CvH6TVGIsT*`VHxlZ;hW&+10bLaM4V)>=4v zv-Pa?JWI?2a_;vw8)!v745bT4DsG0ORszx8S+$9;jHqG{Z!~*Rb_W++tVrk72v?o28G`Lnq2h-}EPX+fX zI<<3TNdN#K07*naRJ(SIsv3DheR|uF91?wVOwLo9TGL@&C?ZSi)#!}|CE}LaS|swC zc8)k9I_(K!_bHXoKk{v+q-(v})*aypMmnQQF+Yh*y9ndECjGdbEY(-U=n`Z&UD;P6 z1N#ky#wk98Zctn`m%|HE6RQblqV;teR!rrI1%fVEJOYGT4TKr~@IKP`DzN?DU#^G5WDiTtyMf5(g8d ze|v-@IZs^02pODtDPbcY2Io`q1;I#a6(g$JAaSY2x-D<1*!GmM zsiBsN_4D-7%v+{#sq!#c9JCvX!3$#!tPX?wlPJt!Hh3rR9~mO>b(44WZb^xQonzc! zIdQl8+}YNgY>s8pM+8s(3M0NtCgb2la1A3(^KZw_VWrCf5mXpB;d1+igjy{Y4%Fj$&LVt=`Vce4>Htz7# zATD$R*l#ym(>Sm$*nDrSKq63!-&yj%T(w4FlVt^nBz&CfS4Y@l-tRA-r+E*LMAR3@UKEXWjMV}N{=frqnkNblIOA1weJ6<1Rv7f<&ts`ZjR6{ z{XU;=3a8(zIs?@c(RMr@2c+TU^3~WRhM27^KWh;O(L^uMY)Etlr72>TE9VfSs7QYz z4}AgX3+Kq36G^+~Mu}mQjz?+RuDmo_mRTCt_%`}j?4{e@k&|&kIC@LHx8roVLUkh=Z!NM zp1w1cx{Td)l{l9;O&_J&zmEPe9bPSpmEiVJn0a3rW_c}?Ciof$Y@?w@q4-@&I>Ern zLhV@p^M8GxMeb8J8hFv&MA1FVtJV})A@J@O3~VIBEF%Jc_D%sxJRkHePFbE-Rt&>d zR-7hxm}-f?o%nBTtZO~Y-(wyU*T~#0F8}4tdvV7C@K?OQDX(77ngTSC*yp8yWrsMy zk8I^n8d#11?3zC&hOZ7K3BNG|)Z^;N!`q0fH=C`|nBv9{cHG@#ee+;pneMyhNwc|q zj?g-6`bil*6zgkSg=3L=ZJ*>px*_?oG$Yg2VXV#-hq61xP+X>lv=|K6jvB-J$v{cw zt>=F^O((?;TxZAWB(U`jQ3)Y2luv|4r)I4KB)?p(h9>3@n zWjUS9iTdmDw$1v*6r36=&q{SjA zCY>KvnFS&DsgR5l;?Q|SzwWwI^EHm21g$$>cNikYIZHC9&6v!AS~;SEj{mcR8fC+% zv}&24J~7mJy-*!%lAQd=u{39sWU3wwI8ejJI2n)%@6UuV-yN*qCf!{}kiU)|b(LSI z02+5YJ` z#RUSIXsxVEuU9h>nlLZ{Vsb-z*WrfZ2miTjtv_I@I>h@kn49(21kNwB!E$QepZ_bq zIDJiWJ)I#G);JzmYL;GgiM=v=IQWb$4>zNA2DYcM;#v5^njAaz{DGmPp(yQjt$QVD zoylgICPFj)icYCTJ#4COGea0nFte`F@AR<5dN zrA!KmvhKffNiFG@~r+nGw7iNV1$+ja^9E6lvbSa?bcI+2invdR3H@UG*2Pq{5+UTrQF*i)?JXkOu9> zw&WO`_8AKufiscxd8Jca!<(X?Gla!5i^J2JG+3%UEA@xg<4i7pfE_F2`OpWOwM<}& zb2>{aJ65_v^}+h_br(|AMjy=pu*W6K&Os?1BUF!Xu%rp~>u`{HO zaHy%HUox?$I^1e^c@R62(MZfr>@4oe-pkd+2h{>gD45qlO{S{$gp{TS96*qkZy9AK z^Gk7NAEAjLC!jo(i-Km6LeezzLux&o#ylp9#fo2|;5b>YLFjqvnn%s zE95aEcE89>pm*=yZ|}D|Db-mSnX4YIajJ^6{U#M+zm%espf~Kzqj4u_^DQgpv;S4` z7ncK3QYw=s-kuA>4Bj*)yVf<*N)KS5KrGJwcjHuqd0z9J_(h_gN7UypBI}sXXA)a+ zvtiK;{FKeg7{bKm=*Fos6KkE6omnSx-K`4`!azK}o82$DrZ2I<;O>PE=d|0CVX2Lr zFv3Z*tgz32)X!kVsH)h|boKhH(}e-U*Yju}Xp$^%j8TYA@+n~T&u+Z0b`*a5&i6Gp zALO|HLG3o#n?z#se!ZVuaXEON{GEjmT}e>Ty(I0T@hJ$u0%lgfin-X zbb6SA%pgVgu$HNkh~Z0<$)tm{P6@kv;5P?z%oOD+XI5#BB*P>e6Gf5f1ltb187Gwr z%+8@MUsr9#g#%qo5rff6R{-9& zpo4qv@^Yl4D3K8~lcpP3@l^J2$Ib}U68u045k_M=aBhf$>d@j)taSbEL(>?!uZS`k zXpjoleV}wRg`LF|Y!mHk@pTj&h!qj9@}|+r1h*UtZXOl4Tsc?kLrt&LdCwtf*GaKD zuo4Y!R$4*rQ-GJ+NF@3S2YDOpg5T0|*4T=o5h2!`HrR3-L4vbR(`rY>8kt}iWd}g$RcBS^GIwuBB3C8+WUgFK zW>1)@d;^FVX0(K#9iUAHiWP~D4ZOQe4+6kfR#vMH0<>0iXF%$aU}{2Sd)RFwmPyFH zv3KucLGlLr{r&fo%bwmyx^sOxu##L8wj7_w4B;MW8IF^o^F5A{JYN{JT5xiSjZ8jg zD}}{PAUyw;HBao1z~+I@yhROfREYF}?4@Vq44~0$uLcfrEwnS@k#KYZV3*8YkX%I6 zOQk{1|H*^eXb3JS9zHy~TIY_n^h>_D3F%48+jRax2%{nUQw+aJ?sD8BZM}WQy@f!$ z>ddQj@XcG-JDY6SL_E=vJ>Yf!X--lYzO;smTUk4y$u^s*eMRP4zCv~=g??xzr)%d) zrRBatZh4kfT}yx&>HU&KM5eL9jo)L)-Y1R#FiZqqH9rgVp=wH|VkqjRW)HIgZ)+BnIoB`s$S@ zF$>R0g|@JN=F=qk6^I6TR+!^OvR&*Az$9(T{%Vsm3dS__AnA)Yi+td|Ga59^1hcLxS*`8Kkml2Djjo!IKI%oE- zYsIh1&(hJYlmP=khFO|m4njWb=tv=w7Pu%|%egQ$AV$8O5r`)1zoMOgoRLJAilRxM^w#RV&)iCY(i>O1iLkiXYji+(m{PMRQ*|#-(?hEo;Te zdc{PTtjuG+J;(UU|JqVAY&k}KYO%(^k~KAsW0ls7((J49;uIYIBzE`iSd|snJ=Vp8 zPT=S^cyvG0Xh$JpABU&3b~(@kYdYY-FN_sJ@&_{N+i#mklZ2VkvCk^ zhdyg7%(Oybzeb$ylMcm8W2*T3P_~eV57fL9GRD7p4biG3B5g#ooG;=z zJrPj0N4z}&%+&oMBl4q3P+Je5M{j4aE8;rUjlp{Zjnm2Jam!8CBf}th-FKj0W!NlM zjQiwecBgL0t>|W^NUi~rgN(5Q_7xCrJ86X*#rJ^g1*!FcUy~s{mX zw%25RiNw}C^f}|>=bHALIQlrRC`uKaGJ~WSLdv4OlSdCEr88`43`Fl_GSeVJkNQ1N zh#G6ZZh7P9WT4QEvgLkRRzZ8_%WE?eO_)z!L>GAc_iM%TR7Ah~+hqI>5S=cuhk;=8*v%a^sbxZ{$W8*wQof0-Fx3qj~5A36(URAR!WyyxshHY+i>YjfN zu~79z56Ku3hKzG2*P*H<*XsQvPdo=eRO-NS0B+^!W zxvo=NZXwn>=!$s_##r==RDYukBg8qw!%Cr+AV)q|emmga=o!6U}9aH&kVC7MTb(_x{I)aObs zs$1Xb$J1!89qPA`|J6A7Nn{URPkg*8q?)W9vQP&J_s~wyXo{0ey$C6xSkk?cZgzjBp9+QKEg+Q@y@6tI?=oTKoidc_ zoLyh^C|jEx6|Xl1KqPL5rcecX81oVMQDjK1fzW%Bz-P4BlF5Mdfce-K#?tgf2@XeR zz2eoKbg(G&U+$4Hsi${ybs=pVyeD%6vtb#CO9|z8W^1SD+b%0vPI-&F6?XpMWW?M4Srd35-iT%zT+F+bv{mI+ z0*^`Ep0hIy%+A321SW^8RTfNQDE6jnddK-B(|0)$;J8~jU#Ak@=o&7BYE@&#sw)gi zPFjz9W_{qSNm$u;Q!gVtKEJQ%t8Ly*%{qG{kSFqi_B}aQrpH$H2*b&$XoFZFvA9kf z<;SuHdzH+d89&dSU~n5ubhK}`bjB5?UUoAy%Y2lsp7QK_aTDXLWU9U@InW%#ei{OZ z$%c^X1N~DCmvZ$n&99iTBk)8#xR&V;YyFM|uT`zfiVyqPEQu$*98)va5v)wrFc@={ zVbM)HpFG@XAvbQrMnCqb7ox=TXD-EEAJ=T+>AP7cBYm(CFl8eH_N^y1Nw6N^FR|WO z;$ckILm$%5x zH3fquDgLg}+#Zpm-x@vd~FZa!^Y&}h<{fKO1fZ{%w$J#mg>bve&=(Z*wLWrK+5#$B*$ zU~z?`!CHA)$4hj4P#y1vOT~?m?75~*gYMflc#*q;Y^j!8N0^M%aiBTe34X|Mj7Azs7PL>>5edNMdk$Pt#5bQV2S_+k<2|l9q0V--xmz_WUA;yjKG~OrT_*h@o5AQZ@pE& zT@^7^ZM8Zu@>HfK1B%|v=#)lvdZMF&VOOfmr06Ii+I5*Z*$sh3p^cK$xxMQ^XW{@I z8Rx|FEwX zRX>Wuw345P7(@>xAQQg$C<7x4K|N10VZb}u4Vb3AsTysIs6tRyH0C| z6R*JdXXiL0Dq%A3WUk^mKCZ|qqX!v&@mZ;@ruTGgj3{D5Ty2Uo4vj&sH<2Yxsa4AF zI!dIjUjiKmm*>Z$RTF(V6f#3@u2?3FbnCS|fa{+fNXi!ZuuNnJ}H`mNgf1Vv-LUs&ONI2Gcd(z8*P@nbD^v$&$(Yr8n z^(L`NpPO3r(Ge&ddz2V2e@)9pJkGf1CeKpMIEVuqv-qSQ?A&2$x})Zyeij``7szZ* z-iT>9nh_5@y&UpIa>sPe=^4-%K7%-!&_wufBo)ob3hG5?GSG9AAJi~TtNingCcS(D zF;kO3J0f|iCe8N8DJB(7+X9?--J~l(SOrQH6G!7i%B*%O-Q6vQUrbMD&QGKM{44Xx znTf-(vDa1dDIfOx%T*^fS^B*LjkI6uhDY{eJK>&@(jhA?C)5f)9vU+GkAj#J1PY@B6KUS)Ysp>q+ z2ZhZg-ci%LlC8FBC!*RTb}S3|h8c|i`O;s?ZJhHPqF!l#mVWU$M^(vb0)k7CaLZ#x ze=y(TMWk=dz6m;_p&L+<5%Jp3sO&_?HFL7tn01dWSF;>VX?^c)kiFcC#Sb2I57VdN zuMF1Wu8)y=r+Po^VvDFHcMNT}e5&2Tl$^$C93%3U1lCMKvf{N@tEN-<^QFx6fXEQ9 zW!2KjQ{O0J(#BMyiF6puRlNHrU$p67bzI4|10FdD;R5Q|?K$RQqZK?hhC`$@7piVC z@jR<-=a)N^(kRY`l=^dn=jaQJaSW&fY!S^2N%2rp_P<20s*x6 z`4p9(^Jfr2an3~aoO4=4?qrmDMo-5Z=s8bJPE`ImJ^%3x=AZ800G_VM$0LCGfY<+z zv&h<1Ra6)>)yT+RXB7_pgSbY;Tg>%+kXEqNapPfUt27t-g7qz^BW~@>p+hh3#mi66 zq9UOXl+bMw^L$FZxHC1*yp(|ySgM>j6z>J9b$xtV16W4jj z?KFQ`k_(Wo!cQa=D7cz%Jkb@$rfcC6`%?P!_snNjdn}-ZDuK)F9j(0TLn`L)UgDAr z*Y&<@rdz7ZZ)}01#B`*WTJW9h_(R^nX9jVFZCUHMCiSPmJV-6vCh}pjx~BDAqnVpu z`DYVJ=CYb4lGH}lLgwd^+YoVb#ByzuL?^DV9cy6T%Y~#>9*l1M^+ilKE+Hnb*i{90 zUn{uM96lVfv@~x%1Wm8zgLA-=9{_Y?IgUoM|8QV$=6*H6NJhu{DvbyYqi4orlw+P2 zIr2R1^;1xff|+;KL+;x@?9d-;muEozLVw_Anie@`x9!#Bj60!Xj0&#LXLBXE9_0J) zji_b$y3J)S<#9{VTiV;;5c9!Fl2^FcNkpP&>U|@T3%SaJOmZeM@I-NC#`igAALTp!72iX< zj>mIKPSdrUoU2mU;9vPayyc@Kf^%G)Pol5N=q|NA8iR(j+Dyi|!rE{I(J`{vcG5*L zPY1`Y{`GfzgXqSM0j!vg-=3u>%W@2GM!=6)(0iOirik)5#mwnZZFdqscJOfU!i&X zFY<{vlNnmb_KgosT=BCbBNjvLm!=}haUkwLdlyiaS3tzFeL>;UJvn&+WF&n_kt zG93ZyU|tUN#IbBVP%ahb7IPft_a|PZ=SHyf^e2*0hq*Q+voMZ@`pjUKe#v5j_7FPB z7iE7@f||VsEZj(e)@QC5QfYS6xYlCEko=!Bf)M>n>SYZJ`%3!VrNj~#I8+5sQom=5i1E`Ui1dVGTTxwRT9h$mjtkpmZ@q(~kU!d+->-?EPuhkRz zD8`R3(DO|CANWwIIUt^t#5y{!rFP*=RkAg8L@~*uKdA6rnVUqm7x}xIGX4M23pj7-*PI^j{n}}3vP&R{Kb;Zz8k9h6+mwP=vj^~=ZyJy-KyhDRb(vg@OAX?^QK_v-FO~ zJu#ct>TxK!Iuh;AF{{jwEu}z*dj+v``&Vv(`>JAdH_|`0Cd<>p@Z1PA%*R8k6MUcvz*RDOM;PF9WE-S z`sy?l#*u$>_wY&8YAwDe?I7xFgV$r(H{xCE&=Z>>vb6a{x6r0e8MzgtH}J1EQQI=T z{A6%W$6IhREOy7>@{enO6OA*FQ5)|zmlUYYgV5irp_sX7Wn{ay}Y%P#Wq^4>D8e z<)9WN0c0Yd#LU4Yopz^nyrS#^l9;B0X)CP?G; zA5S3w9MOq9@kC5l*UR9zi9}8pG9#wzRbm2r=M;=Tp*EBg>YzrMo8ju|#u9U?nS(U( z&(HrjVp+ElS-8G(Q6n>P9?N(<9zNpG4C5khW#g}lhs9fwC%U94VSq^D`c)8-k5)+< zXw_Z@!#9BP06JY0@c>6zxbgl*{VFZ(lW_{<)rv6Kv?JzWzWb%Svn8U$;=sAmF;=3y=XuE;U{y?K68(-6 zO{Q<)3ZJNy#7-kBWqR4nd?%?wXYjc->%RpIe=?%%{T!W-VTxGy)kP~a!*fT9i+V8K zEC{xs62q)zqbi=&3;4{#Ks)5enzGQvbbLZe$;>wE zE(yaT$#&Kv+~I?xLuk8T*!2MaZ~W`OJ8>$XnBvW_Lrw0yDnf1Nv8T&0g@}a@j9anJ%wW=Saae5(*T1NViheK|SrfnU zoaq1f>1Y0HB3fH7PSB6io97#~(s4OLxamHU84K?p>noAgW65p%ru0;sB(@WciNtUm zxuE8*&9Z=w;?3SbG*#9P1g-@~QOdJg0DDjPVXw3m6KK4C0iO&zgiR$V!LE3uJst>R z$v`i6Xl&GHLeY0?l#^m|0Y7fQ-uEi{^Rmlq@-(z8lpY~d?l5AZe|ANm{!{GS{su21FewwY`g|iY_3rEzi~-JrJ5|4dE6mR{9rQ2vhmL{h zkQ1Rt(qcF&kj~6aNm(kyC?_pA?qb2fTK_Cs%)=C3E1c@$K&$5G?zooNlx`$SNdS?L zDcHx%EqV(b{9aDUV&W0~EYlwVRb1OeOq_`voQZzp_n&zA_Ce1Z!A?7`3G;rvBE-#M zvJ|T-raA3hS46O5ZLh*4MJdNew=lo#Eyr=$n;E%!W{DuA^_cn{@vCRp?IhR5{><5+ z%JB5h5i>m=lakE!W5M2!#~AcnK$xyou(oD&^c@AYcY{XwR^ z6uI`4O2QcyC$ATSYvi6ZVwOpz-Erd13YSTA(z0bNx?#*p-~wotIAB5YtZF07uMSKa z^t}T=Y}J<<7W5|oH9*S0VmEO2>Qf>=_YFCtD{ayV7w~RxXFED(M{tKum*p{_K)8Gj zop=(W<#SF2oSfXOvYlD*2r790tW8CJh?OCIlL;sjPcHv$fXJD*{4oGp(e)G^wdAO3 z#*x&_fe#tis6wc1Eg4Lv}_NdQmxJ2wCJ)Y1WykgELTrgD&(QPWh~ z&f8#*{1$V0d-&0&(+x4*Xo3!LoOlil&Y7+lR`4p5x_dBY!tndmPmp<%o~ElpX`_!O zBi?#(>d(JDn2s*Y$h(@RV+4Ay< z5e_93Q_?rc_l)Pe5r?9|;~|vw$4GQxUfjGyUchWShFEd$UOJ*2t?Bt$&&~3gn+A57 z@0{A|Vo*`4wKF>Sa}#aC4ej%Qyxcuv$ktmziPDUXM=IeIan6-nL~8H5M@^f7Oh@3s zMMmLMf>?PqD|SG}QRdgM6;xhvvf)_R_$=%(@+JzBmY|VDTW-fpS^EE3Bjpf{yL*yA zauBm=?2UPw>>(|SG)kGfkL`(T{LhG*`PBn`7kmmp?muGJ$5%lg%OrDd5Qt|;vFq(`rqPH3JWzlFyX_s)mVp2xUXy zoRiOyR~b)M{KwD4fB#qXHLqX6{9b{v%b(N?i}Ql6rIkhkM+6$HksZX95nWSM>Evdo z?tWO3g=(CY$}O_VV0;8@meFX$rCu~e)hwDAky}}n9PTZs|VR! zcMhX@#Sq!;=v}jX60t1wklNV-z366c-wo=RPfCjG8LxY0qOrw@xQI(qO6{h4B zEJb=uPY`VaI|BAYi`3SgpRn4AaMCEHF9m_|i094C4UJVbOW)VR_}$>fE2s(Nq!TDV z34Af=rZg9;__gLFuVkK1wAPZ(!FYbY;HMZ7^KR0TbWhv;E?SaBbAOtLkA>jsv&Kq~ zn!v+gt{HCN5sjWjoH$t&tK2CsNu@`MZxFqw{L>LZ_$YT=UnIx+2BFdL_zB0#vPScQ zt@tTZQR}@x69*iQg%ABrXd4Z5dZQCz{i^^yn{|u;SdwzX%x;rMO=9w6cOQFt*&^Q1 z99|2IJ7&m>se9YTU6tw4#0VLUjY^cVgL9LrYdPU-B_5|>vUj=1{85QuuPh~GpB@ov zXxl!Uz1*5dhe4hk5t(k$*UtmBjBaMi!u7@OqGv-U(B zkJl4<{t^giH;W72+w@HyNXNqa{Mdo`-A|*n`%xIXQ#KaCAPvH%2_CPhPTbsSR=jdL zZ}^KaFe2d4G9jphlZRlhT(=fi#Kz{{JJ)|T7x!}_k1Za?eWmA~3ytqJiOeTgS-Fyz zLCxx$ALcnhrc;*^)TV9Ba9lGtQn{6|GBpX@R1OXeqs z61qu@loNqY9w0H94%{G)l)EP=G+GQC?18VSz*I0Bp}r2ooGWb)BuZ7EbHH|s1R$Ye zkXk9^#Jp8Ko8qD)m&M%Fbkjx1HbC-?33T-7Q_7a* z=~%65g)5wM&%4#iB(iYl>jJ~h*oNAVr|`7^>&SK6T~;E1-} zbJs)@(>?E^7}mwHArm?YMOa}VbKcKBm=tt$_A64yR&2%Mef8w?0f)2@yKCPaHcP#} zde?$V5CH}P>ZXtT^|ka|X5$*)rZ^+JVWC*iy7OzRH3T*C$f26Ol5(A@)r_ujcM%T7 zS)JOe6SitY>^Zj3chU}ykBQbqOeu;Th|FRb*F@rtd}q$|R17i?vyK`QKj)ConRya9 zgMM}Ik?P+}jSii_{Lj^EG^v6_A53+1S69U#6=eYoX`C!`xc3GyCvyX1`KUI@5E|D? zW75$F$mqAL1{w2Pb0+?upC81p2EC?6ZMU7qrrM0xUCcy zl?4mF-tx+~WxBgHqC_8z6Z~j6{NxY->ipZ$H}y$1_a-(LW|jF@jBWZg`SgAw*#ME9 zQ8T3&9F|{yjy@t551C#Yk7G(dxAV?8_)lW9>>69E7&8es`!S<_V2N3Qt3Mjsa5M!f z?LSo_x7Mwi(IZZZ`^Qr*;|d3O?OmDc=TzS?stYo-ljN&B|GKFY@ogqIifL1t7#$)K zlIIZPSYu5Cd;he#QkeX>Qmx;nkdjmQHgbHt@=H4oAj*k09`GA_%n(i7E#>q@jY2|V zQPceN3(s~{`p#QQ<1l*Wx=)N^T6x_&`jz*ryAj;SN{sO=GB+>K_T#Nmd;n+8iO*Z? zp7NycF@K$Dy@S9`G{)owPq6;ol&Q-bTh`${tS-(&Hk9`E#qY1I4Z1arlw;xXq3ZU? zRGtcL*T0)#=Pk1^xT9L>0kCbS&KcdSw1h6P45=vsaZ&C>?J5THlC!Cv@0%p z4aZ`PT)Ai>kS9S6@^mJKi&I+8dV*;c^4Mv~-qndXnTH83+R|8H7m+7nY);#HGTqt{ z_5|JdTDGv)l{rs!KicHf>1Yny`fuM65GOP6^(=Rg!JSw}faM8b-b`y*MJui*!!zcN z8OZefocPxdv2Uxazm-QW;lNi~e(YCv%X_ zIWz@Uotowa!&9rf2mW9!B|kQ2pp^-@Qdk6JKPKt!!oA{d4lns>E(&Eas=2o0uL|0O z6)NHepE|=k)%&+thdKUol*?x=)$!y7pID1s3&f^C$0ue@#q0Ed2j8_&LWeB2&WL+; zUCRfqJi+pK{kQ3XkwKlGa(UtBpR7x4Ad(i;;S zGn0grbGZ4|FU`@t<0sYrC()7h=YReScuqwQH4Djcj6+BC2U#(@5--P5g~86bLN&*k z05>!^1x6n4na7+1IZ?ZOba$k&pU<9bVqv(j_PCl!%@DMo0OV|gZTWn@eKUO9)FR?B z<_lnV6MjtWTsv#yy0Df^dZ}sF(60M~yBcLg+(C~ykc`Flii_j0zF{y1w0t(eF@IT4 zgAa_p5=dBnYJw z1L-uoa1GTJJ-WD-Mg$4T5R^F*efY5@Bz*fGl54!+mqK`h?StXs^MDwZ4PIn zazO!WMs3L);0%?C4$frHh85enmU&}f9S<`%_244~9j|u=myWT^z}|pa9$}mF=MKcV z%b3DtM#9TL%DaRC){Ytu7O{i}aSfT_WWd;jRZd4#B(&eM+*L_zNHR8i3|GmI55Jcv z$HgkF>`N6>U+XVC{N3;VZdF$JzAT=0@SbZmJLcf=XY5F7ycR3x8}~*|DGS~W234P8 zTiSOuS}@F=joBeGml=6gC!y#a*bfrs#V7Z~{Upn0=v?QzV#a;%5*_nyIv_sQhd{l(=#By&euBL z8@A_L=EF~&c8@TM+BRO~1{riq5j;jfYvP$Vr(>hAxj9{B(M;g@C$YK~uG@fB?ahJ) zB5%5?+Bg;2lP7!8SPAOcgnfzP6WhvplER9E#!0B6SgufacTMC_pRDl}|G!+S_R-*z zn5>|9pVJ2K>`84&C>XU33Qgom{#*1R=|P<)%%esMWJJ=Sdj(`MU73jPb29mFW+eHi z$UG+|yZ`yuzy74xpiYth_&v(x?x~pK_s5lm#xMLQ)6NySu9KA*wQf2b&sD63{@vvM z#jtOOgV7We$G^5Ok7GwiPfT826FMYqZB1p)14}#YsSWwBuO1_vaVM@%QyS4RY$mzW zldNymfEQK)3=+5Okm8h#$%!1DdNLT-ZDQ5RS|tv|Rmd)nYIZtx;WXUzncchK*65BS zD7z?d{h14V+tBH`tOaH4fx3PY|8S2a36EosJI54rCvRVEBqF)`PU%d;>7$#e8lgPT zje8-Uhl&ZDwJY$lo0mVqT(c54?HEv4VrmqWX=L#Xqp-S(WTQ{td_a7lyPj6Epai+l z7eYfR_1%8pGY}9SvSYymyqrqVxl7T|UX*Z*of}E#ze<|zWJNL!H{^K8J^lzzdYc2GqCoh(^j55t@bb^Nw*gyr+a3) ztGeFbx2vfV>WR#!y>FOIoJ`~a9m0{BF=16rsd+_myKMCPoXnc4dci;UImkGtPbR7n zPX=T21b61jMUnv8r$=liOARjoN1?T;xHx`O4rKJ?lN<}^*5EjwALEmOqu5XWknc#2G=>{*>q`&2h!uj0BdflXr9V+w>uJ)fdM?h5 z?^k}-8h5C-&_o(N8j->u-c=+_`}2{0%UPNX=a?(`hE2F{WZ5i27ycI8f0 zHV2q?53I3&GYHniwA)~i`Qp7RU*ECGD5V2GzG#S~TRQiq^+<|=IVMx8Y^zwTcz*ts z1-ndpb}QYJ#MmoBbaZlz0m?!_w^0RFsqAtXAIH{ei+JI*4McH4iLnLTO7u%|o4aFx z6tQ~75;rSOA=@Sq+F+0UO<}1y-=oy-ch;%p_a0|J4XxSEA|9s)f*E%ov*JXP5x9sl ztR4adx4Aa-o2W&hC|3>0Mo)Uw${E32lnuO4CGSS6$o|CTzOS!=w${eF0!aezuRQ;0nw$JY0w}a29dswMmxiDd z&eGtf(UGJ{L=)oV9Be?hsxgaRhVc9BEsXBnKmg~@8CU_xB`0SY3g|0|3wVXJbwrB0 zyQUQ0Mhwfrx%Fl5&uqjO?d}?;{$1{Cn)X)A8|TOBtQkXDA^`kELb6Xb;o}Zr5!|^s zl)qu1A#>GCByxfm+phF?>ZMi43jEx%i9Ys%T;)kmcn1t`oESnls&G!6!(-Fas8ou* ze6SfO=Z1Tw0t}JFc!`xAGZl66KVwcH{`u|t=f%|gLX4sb-c)Rt+BbVf^;q}B*BXgr zO`GQ@cQ%ATpx!xn?7L(WJJscc-RA+`_vIFV)2^8~I6pr>=XrF>Y^L{!Uz0#j1tOp0 zh*;G7X4M}={Qd@BR~*XG@-9MqMcRsnUZeiRnMk7U-NHz$WLPs@pEdnPzY!Ivo1C2{ zKeOHZCHrWuv7$C0F>R2aC~GNG6bL2v?cOra9nTSmMk!ZX{H8j!2*hg9VohGME4WAOJ~3K~!`h`VGG- zlg3ATgNHn_Q=W{6)EdW-UUT&agNJV-pvRXdmxv&rQvVoNF(Jw5`A5iz~O#uL)U%vw7wKFnGVu{_M;}G3F(kgmV&72_^nmGs2a|}bBq$~ zI8X=&j6CfLy2aE!S>>2B4}fUlL3VpN z^C|k(2O>&S4=eT2G7lruw@*3(lfj39=P|y1raNA8WpBFa?3TZFrFY~Y`|3!iV_s;| z(I7TIxl1Uy%;5N(JRkEaj1=V~xj}l~wf2kL1eoz;qGlboR~V&R8W|z~9XcYa5T1$f zcRIL(HqNbt<8phM&|E;rdko!@s=;b*`oM$*BG-+M%9=5hu7VbMEAem2K0hc89j z+&9b0XIV6+ldL77$S017$o43fagVTb+gm;M8i&Q@^b{*(!^|YmLt7papnCW&y4{l} zEjHuSsNwi&hQU{3jsrXgTsy`9$1*eeI@f>zC&Oh)SA^@^;&O~c`h2+4MfMIMSCf4W#-f1AG8+jm)ajL*V z^TRnOe*Qg??^Mjofz4yHnMfNuQ{tHR>NxmRsVLWiTd}~D#h#4cUyzM_&myu8^vv=++~z?n}8aE&N4ye88VJFXx5W{H$t4d?)`>c<4sL_%R`uR^Xfj`L#| ze#oeRWPJu4e%k(F6eyk6ASq$8XLtm_VVlR1l@XM5qCAeOS~ZlNeO46pCw!IBS&Q9{ zz5_zu_WZ=)S>M>{Kr{m3GSp5SqZpH#-uY{rbdfO138IJ~W~S*Nh#h_NpZBKHdvZeH z)?jvt_0uRwAvu;Va&Dc&tVCqsy|CplJtESwoOBP&{`^30uyZMth;N4NghAZlps;hk znRs3edFW`F?vj=a74Mm2?5SC{0m(BxFH_!>;-ga7qk0dns~_CO#Pq>=yJG6(;Xh2=0Wx~tFu5w4s|NnchmF#aL#Ags@k~rrp$GYc zd=NvN(0QI2bQLLJ2PbCg?dYfkrZeW@dGcF3gP5Tp+uU#*Bo|GlPvKf@HeqrCG7KL( z(yy6ml)k`2xr+KU4(K7(b|9X}miM&1RXcfQ`LS+KI(CLKqA;u8o?CMnfQ$P7qS58c zZLj8@r3%L!u*ugMu9j)Kw9MbkHtjj3n1I!8Cg=6l@udM!T)XADxYU!EO&YD7Np_?f z3pNO}-y9%UG;yuZX(3XWd``N@4K!Nc$_=4k%l-7E2_TzA+`6|iKdTIm$%shE`9}Ww zUtSsZro3`!OGeIF3+{02+3>CiF)_~*fq0uj%-}&mu4MX_JeST&d!DVWIbGO1O=?02 z>aD^m_cqZAYpC-UD0AKy8OmbGwsr;HnOL9C|F7Vb#x(R?5hfR?G_>eLLon1LXg+5+=7!>u%0QSWDgim} zqBO;Iyid`ZgyAk=HsIQ_`7H)8aSqp0P-g5zV+gA5hJuK#n9t>(mkP*z{20$W74adi z;Vx%CDm_Hzw3C{8U!u6=@LahW=l+LDAK14kDmvr&36PYoagNmB%VZ(Z`Yee4NEW0| zls0A)^j4P+E18a6@ktXVV@z1~Fc>6mau2|X8`>-DOl4*7syQ}^H zwlc1EGdB%gE{{RzF|3XhchED5uTt8S8CIg0PH-f`?-v3n@cXJ)D=b15-kPHb+`H0S zV@T#ORBs$2kz1X%*AE+At{C*trHQDBqg~`Uq;TWh;DAFdy?P=$n4K)~C`DlzakLhi z7$oP$D1we1%}#t**VrF{oT%<(=6KZvPMn@ef?I@-i(5K9*~NPZnvcXpW+v~~IL|ZP zhl0m8vY;nRHu_8Jy4yKm%gzB>se!7iMMvv9{AVzZ}zUn552UJX4L!yuo}ksP{h zPsz0bsR<6rbA3o~hLvZ=DxVzVZO_Ltw9e!cU&LI5hbtWsljl+f2mIfr2U%U*GtZG^ z9SA&wi2lW-3b?KD%bLAtJWsqe4GpUst83K!W%^2MG7%dp>zi_X(*xr216tJF90JDo z9c!h4-XUcHN8t?;oMd=e2&SA;bOca&c^ly`Hb; zIUQ|(u}>_A2zu33YTVzpvM0E|V(Cc_rS!}hcWxSU)pcuxP7MEWDU?<~Wt;~OHE|kw zVVuq6&_I{dWwF!h9_u{Bc|BV?b-nH>p~zGPGY0Bw@3|P%3m@XvM=AtswXw2ns}nbR z+MF$zyRL52nklTB%P^uuK#`tmS6e*8cvyXk zJZA{4x^m_K?e56JNZxf4!72@Pl-NE_aUWCVG4le){tn0N!7-%VHdCeG8=V`|SOfMW zL-0)DgL5@K!A*{Y9Rlyc+cRz4xm{5N!4b)#Zr33kHeAG)kF1^XOUG6VjzahP=&Y((jpx!aHDHdA7GHA7S^VT-T8NWqk3hL!fNucQMQMWJDNGCsn z{J5XM2H>5EuIP2>`N6NdD+sjzG>f!r0>HE5gYekND4qPQbHh{k_&9JkYSqATYh+4! zHuH7r+Wk(qnktz`3VA`o+z?Lx#kE>zuI#qMD|>z$&&sNOy=H&%PPK}J!TO6R`P=ER zl4(R&;M`%#R&I*1q3*TxFL$XMNISjhz2m~LT1>be2@Ky~rLW6em8%wsp27DJyZja8yTX&OxeXtx<#r1*n!?f*F98L9* znkcE^)M@NeFrPq46*zOXITyE^agIpEuDGUMLLg%*H?blN`EQ0o4SM3_{q7>B3O(`j z6H~#;GZmJpT{`?##@E3ySD&BIi$h6E`_RK&(hIC(nvH#-t#1RhL&x|H{L=bhDMHRT zF+G0AO!Wz$~;?m+W9&>?7=ui6%6<^a<<{eEKjVtvnbo$j+$%s2KNISB)I$9$Mv7Qe;N8ee4V@a#o1*7 zGV$|-!6>o}S-PcFxp_FPdEwloU&clF9-2K;FG4ClfwLCwuLNaD35m!v4hFym$HfPA zcqo#_vY=2SwjrN_B_WZ*psD{#?&Z&!;25lDksk5sJjImTIk9CYy6l%TdF`*)x3GY| zC9rFqvH|6vxq!m53JV6D&7w%9qQf5J?!EG7`kG63c6m989g)Jlj`uc;XNV6|%JD}F z_xrn$TIQ3ZQ+UW8N1kl&_xnbdWODqox~S&U=8X{~Y2xnI&7+h-C0}y_-Rj&tUsN3g z3YOUQ42;e62vdh<`JP`yxxNmZRl?6b5E&{UQ$3k<75EzJmZxRo*k)Klgn_I7+s;lRwaVSAe-2ZSO-Y&jiWVelQd^st{GwG4; z&(tG-5Hr93V0bDrKDnBW@H}6D90au*Ik)BLZbS|w+KW81&@k-}91Y4h@h?W2gBE)(ZbvZuv>j{U zrGfn3;{oPco-iMbT!`3le{%b2$~3BDqeGetNM9*o5Qu$p1eWu1K!@t!3zl;&f;h=J zi2^NX3}$+;*!UamTU%Ax$Rs79GwL-d*>;u8NaT^*SzSq94$zX!__Q~CaHY~yRysc& z)z}QfYhT+~I|r3BqLpnI-v;Eam(p?^fDAL7fX}n{SRl6;NcbK+yb3R*ZdvZ z>7dI!q9UQcMyw#O!DI2(CQ`1(pl3&wpna^(ON^h$t0)6H11Wmca(biEhQn9|(w zs;}RV(dwuEp^{{keev;B7#07xyFRpUP(06~n!8RlOZ*J_^hTOzoyskFgfan3u zFSf^9PKRA8n+Y_QMqd(v7m`)hj=&hp%^}Z~G`pf0%cno|+4#Tkr96iTkdxwfuEz`C z{MWhcu~ry@nzXhVXXa*7B5!bvC7;9lU*ZGH=6Na_NJK9iCA*;vIoHEoe`nJyP?h@M zl;NE9j9f5$Dc^fRe?%8FKKUP-3$-2(Ab8w9Ie{yquv}4(y_Rr!A^cmb*}t$av~Pws zTdFUOI5P0SOqS?Zs~TWaR-}}k$w2;2y(1hq`&e!qnjkCMPh5{jHDe39+Hi?hphGL1 z9vSEvLiv+AqX}yv^LYXj{Q`I%-eja{&gQKH**0kK%it6$6C`l)E9q{&lcMklfg{ex zBnF*_=!nUj2RZ$!(yd9L=!iCNv!kCsf6fntjn5v{1ktZUCZ{3_Wou`#dMhkWGjpz% zKv90Uz(*WM_>TA(*gy$h@>mL#fxLx z#a#Q$z=LKhc;vd_??vLPFfg}yy9d~Ip>)phX65OR$G&tU(#e}3#{G#nL_axTb5$ZN z$LJ(v`?phuvHoosR<>Jzd$f{~$o5_I4P?4y^bJASNl9;pk<-7lpT=-+ePoPk6?|ie zPAe3YxeLae;7+42y>JKX#u-pg6gZ09NZSIR<9`uX5mE*U?J z^YQ7UA@%IYdYj3OKmIJ8+Das_cISD=#3lFVEu)lNWUUAanlQ!Z*yn1l6L_;_k)S41 z`=_~5D4}%k_+2M;yMl>FeaH^RV$~L@a-9BC)|v{{Jd=(UNmHixUqpl6LcZFqnI34E z+_WT2JbCqjZ<(PIF*!3+6!+4qP2I~6T?l3<5Q1M&Q0a*C6?yQSJi6Ps5ktIHkPZm% zTiulrhblX_9$b;~jA6PP0?cZDevEyhEZetr8xuE8>6oPO$iSd|1aibM2pXc4(h_gk3b2wsoA z#vW6y*6F(YFwapq+&B-MB#1@$&l0V`b<9wlovJze{=3`d5Q}TfO?75@;Mp3DtvdwEr= zZC?6pyFzI|PyJ$#(3@a9rr2oy|G_BoKY^HNG?1>7ahFCtQ0#Whsw*d=3lo18_tdwR z+0>W^b&h_>2Qf7jzazlMlRb?@_lq|W!0E{oGueUX>3X?Uh(R$5fd+?m0@ujs{QRKb zO1TdD$^3nhGsSF1YI$l5=$M!|(-SzE&FMZZe1QPE6@nE>OSPF3uQK0oQymXuPgFFV`U7JK*39_ zjOh^|G*B;Cnxp3=CZ-ym+ES^7-(Swl^E^5)G9zB->147(u{oIW1WnQPmLai=Zla+z zcw^%taB=2F?@ZfWDf3rx@e_Sw)HeGkc_L=|mr^}B>aH%df{8|$8_TZQVG?%?I%&lQ zGOE;r4%Vrn0k*vsQ85{v0y}Hm(7E;=I^51N#~AivO*AX|j{0ZvS&IOQ^nL}q;bc_! zQDaD8ZTL$Z)6VEL5KQjmoek^3@b|8FbE<$ts34Lb>~j-NS2nyJzg#a8(Mp!^3FX=f zRX&!Q-yDUBF3X|&qg^@j{9FpO>*kJoO)4d5(W7UK!FWYraN(KegGy?+1u&4vFAv(a(Q*z`M z-G7h;Bz5wU6*Y{dL!5)ojEdhX_CM$6R5fwYj!@q9FlOfG{Gi`H@tf!Qjgv+Cv?oM^ zY!FdCh4h+&CHTunYc9M%*&kz(0wD3N_gbP)otS|t*P~%~nOB|zJ}te*&{X=+;2OUV z1+wdJA1ZXBJ8^&(Y`XQPR#(Ly%G(@ zQe8YVIhc+)It^C#tm-h4DXH+*^jtM86LI`%YwiX-3V{ScL}X*AbbSV+vI89q8(&|5 zws$Trrx7{jxz9NoR`ONlGC8A>Xap)YyKKJ*f%P8GS^pX_pgyX|8G8Iik435YMh5-> zFSl@X{tu5+r(E-ONA0GF{ZAoVfi2nvy|#(YB1!dWbH#eB#_U4<(|9ho8wg$onzSa) zdjn}X%~_p7pG{QK^OG|;(Z*cWmzEaR{c?s(@AoGdjGD|fE6%1Wk3{C1jCDFPxv7`Q z*|-3gm&ztUuJ|kEMS3B{I6G0cLCV)n*D#$NvOs|X8PV~U6#i)l_Jf7LtGI$CZ}Y}r zke~+>j z&twJ$FAtM{NlSbHC_E*tDBc*vd1Mvlbs!NBYx9otu}rg= z3=LUE2EA0X!9j3)c$^W>BT!3Q^|N|olB0={{Q60is@nk&V zff1(p*i%q>G+@e@cPqxY-g4=&Tz^tnS1r4uCYv!)%46h!G&8ElXD9kvna0T)B8C_% zVms#q@Gz(-k%Z$N#Q5iQ_D`HlK~UmJN+c3pMIYK*rA&Zy&5mbM$vSg7hWWipvzVI7 z$)iZj?nX}L!$e^m!f#2)R;No0H$;YMrpRYu?m5NJWf2Lcm?c$Ra+TZ$&XL|LcA zi6$3YVaXv-5L~2Q=y(xjCL`o5PVTL?seV#7Fd3*cZNBq7H%K0 zJwvuU9b`-KD%OZhzmDkxj?Ip8ur-EA+70KX3AS;;;S9;a&7xEq#A_nE0WmYtLRT@- zDvrfPBZ<3F_33$Mf9#79cw)*E=w@v_u&FgCDsGzMd|(5Aq422i*dV)4j_xkM1+o%A z&MxRQ8nK~Bz~Yo=-j1CEjiu2%A~)BEy+`*Im@Q_#y&8RvSEkU~jSw1H=nE0Sfe67+ zPWjcWl4IbmD513@=;H31o;nUbZAdc5kq0l@hf83HgxS1GTeW{Kw+5nfA19FY2rJgV zs?(hwLV-8NdcL~w*uMK6hx zuM>$lVw+ZFo@n%2Y{MSuC9(SPt~QZ@^uz9H1Dzt#-P6Yn?L66Ng2)i3 zx(_lUYG$TS=95Q3@-dkiaWH+(!8=a|;twY9oX(8?-y^)mAWj2kUY#Ti*QKlWtr4#8 zgQiQSQu(cMwxlhThK;W%PTV^+SGdfNjD82eoS2#kJSXOre1Oz9X$C#hx%y<5@7u2N z`+dD$j0-0aRjM=r9 ztg(|anfG{;3>?Qg1Jp_JA)o-%$k1Wg;HtJZE%~^ATbWxgRFYdctUQWK*Na3RAHuQE zg9e&slvc1GotTs^H^*NW4@@FUEq>8&-X(z3m`5mhp)D+-<#%%=to(NWrrieEV2v|)bN5!Jzs^?4Lt zn#mOO1SV3@L%Izmy@tOdQRP+*KJ{w(HiM`UE3pNi8bgdvYHrp5Elc_pGE%p`P$mlK zvWFXk17yZKH(-t(vc zL|d?kKy@4-j@C1uXby2Q;%OI#QJ27S#|ri&@vM(OU>N`aAOJ~3K~&ZodTb+dS+1o~0(foGe#TxAIC{(?-Q4Q^SV=i{YuW}V*Hk9T@!Bk@@|+Sa03 zku)TQtxMD7PZ^}+MwU%q3;rw(FE5pjvKfi4=PK28q_bpfQu7tVor&JmP2L5A_>3^t zs~p%I4+C{3o>1F%ce4!Ilpk28f6fbgxgZg97MLv8d`F-{7|-0acX)S+9dLRMhaQh& z62$TA9BJ`^wIMl?Z9FN3_pcVq-%NSKNTxO@+eIX2^K)86)d2iKPDMC)UtKb@wx}b=3&q#Isdx*{sWlP4Wfd zUgC=fF`HcaiG={OuBNfSRL{ixQ=C0;Q!D}p$tlnF8_wpXcDOI5J7b11Ymi~ z|BmT=sDC41^M|EH#)-F0R{M-IoM3Q2Qt2R$8(E|mm~SqHdSm-tWyS2`jm0M!+61&-ak&;VrVLYYj(0b9JdXJkCmaaM<^&N? z0XUrY;!@Okj=p@SBAmDbyM1voB1@R1$jl?EoCHE zBP>qcK!B|m<4I>OidNFuU`z6_ zOCj+|&X$BK2eIR3VdvvApAer1#zIrxtzLg$%Zqth6NO|$2%tv-4HlGhJ<{&Lkc@;d zLV3*7K6|QSA~9>69+&cKr!w*?;;%t73@~4t%r*vTXJh6?jPWu}m8MQ*F{&^&_GaY7 z)`4)1#G=HuKqvR?*xzgGtF>a4F)q&boM@aMFDesD=Nir>1@o04Zfj_J@Rg{vGfn?6)!u4T_DMeqO&C~PYt zF}`tyP4t2)<36h=u5I9YPH`i?6^ru6qMNDN!QDQsKpMj1D zA~X`c!5{P19-*7k$U0#cJ_N*GfGZ<2a*GqJo?nB7F9uI~9C|8R!4r+St>VczsjYSS zf$aSZcZ^^*$};m{J;H2pu{W+dLTwv`1Jb7kcy`Z?QZIeBv)ppZktn_Q@XQVTnO5a<)WSK!gkQ0zXKxT_7GNBr&tZoXV@ zVtC5n^vq9q6a)%?JAQ>2E%++^w=ackmUZ8M1)ssBL1`EJJWzGyrF|GE>LF{X@7i%h z=VEBaaON;ZUPPY41HH>i$-}ksa)>VRL8=+4Vmf8Sd#+LQ5+;T(M$_c+$A>fM&${)u zx?GJ6OGb;F95$QSM_4tix9*#{#|>9>V#@qu;gdwZL?~5n9JX@%gpzX2LepRf1}i$I zMo~{(zwxL`5L%bXP<^ADhQ}M%x23nN`!k0|^gBNWJnP^_I)8yBVW8I=(U5r_)1cZj zyct;O52II~BEz`t;djW0_-(KvABay&7AkdMMpgm|Br>~&ZJ=(2 zOdy}4W|=k4^gaq$XJ4aXPjVY3Ro(6^MouKy;CK=5c~3WaDv$|cW}uRgWfg!v=S=cm zDi|*-vr?3r3Y~Pnvu`$Oq>VzTdC|@{TT&2|$>-okIe#L)VtseccU>32g=)r@Af$Zh zJLh9N$s`vtT$AQ9+p|&P8DgZm_43SH@_10IX?EY`=W+GEJO7+9LL;TOeN^9r%T>XqYUfdxFuAHi6U;4nAa9@zD z*t>brm`W?a9kr+J(0y5#7rfG&w$M0}`|C96T|H;TX3YMDlinVQ--zAmTj`;^#CL4o zBQ7wybqMyGcS|w9emY**2;Ujr5u2%l2;k89(zb5ZjX9u`Cb{2!?ZZ$p*{$(&k#BcS zk;*#$Jcn1psXwZA193Ofy^Y+S6tf5s$qBQ|y$9WKK<1lvJ7)>T~Yd!(%P8? zi*p~s`qTq?E3Tl5`9&C9!TCstWA(t6uFyF3#fE*V(7Ate;q!8@1$!(i6P3jkX{q`&CR!ny`N5gS}yJz|8@wq65(f@rpWef*rj z%xxp|u$Y>-lArnO*yMV78a_lxzbq7{&M~ z&a7h8Q^RFRGuh{;s*lu4QnbAr2EF>RUO*t95rS*l;b1Ow-?WCwD4WYlHTsc7o)Pb$ z_IP{s7Nho-eZdKlV|#r}1e2c-d8;X`!BCFw2uK)Aq?H)Y%J3M+8SC>15|{IM0#@vB z;_Py35!{{F*f*P}TF5Z%u|6z~)5NOOnU^^v0}I*ZDllgx{cw{;HQ z1UjFjZJO}eL0$Urs8K0LWt{g;*14Lfywk@yWO<^09ZWrHE5%^?^vR&LZ8A1|NzS9K z&Zy+ZE#3&3{$vk)mvXsFcaNDncb_b}^5YTrYPdb)s1oyRAv79895FVhD&2x<%k&E+ zQg`jCFm8J1P=E$(7QG{XSVRD66Dal97p(?%zxK=AdTja|3E-yoHAWHHM57)=HuE4G zWb{ux37pey?u8L@RGf%K&JpB!&0o`!0lADZk!9yi=O36Wx*JXP<@VVg<)}}lJeniJ zArzi8p#aJ;2*&d~rFjH;XtD+L`}6!vyp|Nak=xr$XkqmE$mXZ|L39(JG_pe>3zw0e zarGw6GFxPh)$*0G;fwTyQ4vorL%Mm~ED2(C=an2SqI7hF zK!b_a>EZhFV-4pUrpAVLq2(Kz=)p?{p6Fx4`FSkd6^jF$^w;vm?UHU*G(Hh@RjDpC z;YLKjyxLvh&w7d$C+8exyrF}!9R3|WAYMiv>Sx2}vR)z2NY!@k?v6;ARe2FL-R!fo zY_6IS4)zqRYl`n<*Gb4;gxc(`jsM*&C5;1Oshn-|mU8$Ver~(tJ(?7iCtBDch407OK%~^4d1CT%BO3kDxh$?=VO=f^C@uzWijNfCa=Aj_Z48Zl>1x?h*+TthpxnPwI+~HA>HzF_9R-C}?xZv4JxIH;$bBK#v^1EiwkK|kWG*^T?&sJJ6oOjm3MM4rkxUE_#R*r2|o^c&I9^rc?!Ggz#h_g8R@z0F=H+5nvTe{=5*Df;n3VY-I zhI5|F;9ouDZg-zM-k#d`<&F9<_)f05iHXZ#4dx-Ef!;~#hr}=l2Gb7Dt!kKiW-UdnI66(b`aKOS#v&akWa zq!aXQ`{oaSa-8C#|F1+dCzwrB3{82`e+b_oRW6SPwHfcoQu>Zs=NU^oBW}V$n}|40 z+0w_xFv6K1VU89ZSDWRdsF$GqdtE4>_`D@IX^Y zKMpg`LM@dogXcZ7=B@fc<%vG$%%IIoM5FAJEI<%i)Y zos{*_6~I!#T}YYEegAKQdf7E;wnP_7eUsUYd--1gDH+cXy40@B2&_BUJT1aG9nlf| z2IH!@0ow3oQbVwvN?LH(>!qw1pcA#sZB=l6qQY$sPy88 z(xk1hXr=?StK!enKFAZlMhPf3dw1#*p^m&wLd5UF{EHULYk+p~$JN%t8NYHop23yn zH~|^EKxlay+e55bqgAmZ-lOh$-iR*+#On55imJ569uO8DDFQNet0%? zs%qGlkh(iB`e;Uu404Gh+AR(*0zJR@_t6^FB% znZZ3eFUJJ!)uMOZzz4gnOExkhj=bM($ymAbZ|g4kN$KDXBDKiB{ZEF&QwA-+N|W~< zfE(${mv>}ujVFciA7LE_AlYun5qESRcako7YP$ev7X~9v>^J(oiRi5Hv$(k%s5tLC z)y>K5NsCYv`prABnlJus{vMulE$B3mC?6RlJ;%Q5jg4~}x8y_sXtXty>0{;dw!{t{ zp`U+6L3&6GlM2=aP~*u?f{Ym^wg~ErE}x>YMGA=q25LLM%Xx!wIX80jyXghNj#nu; z{P$Peva&qdR6KBGYBB_CASDZG6HF$_e)~*_(G(@;^lANQ3}Q}sFf*$#MC5)C-v$P> z*qH$onN<~**p%?QBg-0#DfGoO#cCW73v@={8qbkY3rfdeP9Z6_vw)XQ5jU6HM;mT{Bq~4nH`PEJ?h*JG+e)pQu5I@h=Dj&v4o{{B@ma^l#@q3_s zY*qLeGWG#IP{GaiSZUb}8FA&{Oi|^1tupQKih_3JKhE2Z;0rqZJq$hex{N!I-##01 zS1KH+Cn`QQ;+@?pfP4Zmu3agE_bpt3y1yg#rUl4J!}W>_148w^`1pGi2YM*^tNy3N zoZ&hN`h4P)D@=g{ul#3I=;e5!Ex6CjeHaLYx_yRdNDe7Jx$~V_oIM?`> z3Y}Z+;@opj29if{8j0elzK?Hftt>i-KJzslc|JxZgE&!q^WT5}x902uI`9;-3RQEI z4XQUYlLM#gw!#Q0Wg)vqe|);9{7OUt=X65rAQEk6Y+CKuO*Qh7&+}AeL{vQyQJK&4 z)3f+RGNNB{nqEW%-*fu3;lMoS9HRMK711GEYdve-!OLovu;=fqby;(6kbC+v8ca9o}H1QKsAw z)Vq1pc=6DQOdnMKOdz=Rw8ByO9w{f&Th@V!&$?1++kRG&nAyM5an4o5^=?|Q^*zrS z6k+$Vsd~h074k*#j`{~eJSS28ctz$A3xJXI{(K25aIT>=GQCyx%Gi!p7U!KIJ!f=t zrr?k*K<8EMqS~=oX(hbPx@!zm!97Krs7`AX3YFj%%5EsGA~uw+JT6~~pr!+-WF3-5 zH?SkZwgPA?^>ilO{I_ip`y3=^*K`B!_Q3I+tBJi5UjuUTv-Vtbpc3hHhHuQRQ>?9K z+B(<*O~lu+J<75rZ%rx0PxL8n>ZwM`vI4{s#RmH)nvvgy)DX}aNLWkSZK_ul-%4}D z_Q{8OLQ*%aDrpNGcqWa^qfe`;y#bMAd6p_zQ{^_Lq*oPJV!apgcFRCnwBpV7O*n>T zF}g(Ez{DYr+a>*R3I`#;`)o+KkwjDj$=+++@8C%#+$164Ec)2H5t*^q) z35fTI+p)`z@Uo&FU@Uv7A1s0|KOu1Gw9=)5Z5&Jv^BVcoh=&u;NHh@{orl3>RYf<< z3h4zl%+5sdzB7$K5~z&gWdwmL^1-2{%#4iCKn_-UkNA)VBrRjK*t#L??K;V-Pc$uGfw>TpUS7az>GjKf36N$bEY%04{j`wYL`a~ zD-{590H&f7@)HZz#+C1I_xer;w{5aQIITg5h%@Ayb%ZM##PRWle9XNb)(<=PRGtvc zt+~DoiF|-r(B(*WQ(|xSU1`?i1eburwJp)hbu*&sQm<&s9GV?``|4?hq_Px;&M)bY zaT@D`jcq*eF{`NcSjGW&TGdW}jcX&s#`BzT;v<};ESL36pQ)@@CGp-rMPxOYCJ-9{ zi)7A2EAya6>7&xPXNTAix_6;lA?zeIVrTem_*jZ{3XuH#Uh#=}bo`p`KCOt&ix|CM z=h{uD(z|K5Bl>&;ja=LaFHX4|&m;BJEVb4o0H7ZKMneuCLXXh9R_5|^jynP_7+LbP z2ceNRwEmp4?THrJS#-D{Gp<~^z|Cl#zD_3t72k8h88n|AK8*S`Y2lu z*Ja1cjCCtT*Csq#dZdAzQ;n$xs>no2vdimt5>iF}j(Ng!LmPuYoXS9Syl1wRbK}F>BcC!RD2%kXT6wW~EcN=xyIp@66@=S2S@7p}Uth3oX!R~H$^dNk) zXq6*1$DH9K8`Z4{oE~IHR#zP|&;R~|!-tw2Xtl-c1A!jS9S`@H$CG+_&7u+~Q`ouUQtzB>7H~g3&d(bLRQ&aQ*ze z8og-_r(VxRPHgO%L`q{$W-jPqQy!j(z(0v6EtrHJId}V{2Fw%LImPXWlhk;=Vw^8HHD{h7Z=M$q__lRN=`B zJyi+)r8h%{MH*W*v#Nb|b0-_0!IsG*$Qp=L2p5Ccz$O?v(08J-Sjy|KRPvdT?Wv@?wysL_PVbz#DEpj+2RWU95!bV( znu@%Gaad2(e=Fi$r={j1=0*}n>8$$q!?70UmE{D?}YgU7F3270kh5?DmA33C^y?X5PboPrB*I=%>N^O+T8Kz%UhfB-NcJm7NuF zZF|n0a3!ly+$cff)bWydaBHAV{W3&eI59cm9`(ZRgbX;^K8+hKeg0AxhX?<>gwNI9(Z-t>!9xU^=nx2P%y>6!(;UC83mF@bc!NDU zJ}%j|7+#3ZIJHC&#Po^0hJJt1Qy(w`kMn*M2{^&oxCkypRPlR{#)`@miV;BYSp*Sa zFtwts9SO50w58$uv|WnLXF~j;k^6ne0Hs@dvCS{#c>l%?V%c_(Xu87Kyf|5E^)wEr zN{HiQ`x_$qX2j99F&Uzce>#`==^S?Fa$$!B!*03l01(xXyNx>0$H2T0#ZfVX z7u5hLd2&YAoSEY}L%g3_X*j@XrbUmZ<4OGz-4jPQemY>5_)%5V1`Rzb7lo8`H>bfV zGjh5aI1qq;mW4Uj(K-E8GEOkN8TaOzIb%9bmt!QZ%s@5>_5@v3=6@dMF3SNo}Y}sm+;FL-yPD6Fx9L|2ZcG$l!9Y6L(alarjbN|8PT|36c z`~+@Z#(m{+rS*H>j+h;Jo})|v03ZNKL_t)6I2$&uh{#B#YpN0|8CwPf3Zn>+cP)^Y zx*1~%ZSy?QcFVV_4aSGl!_C0^t9l(e_o&*8PMDaeOM;9I3c`gXx4^%-Sq}s9 zrvH$j(ze!?*|&$^VarA+AAeldUIh?O#rrA^>*Dw3p05wk%LJy~$qxrtQ?pi0zrOVp zs~>l%WzJLVNXTk|OY}@B$@QbZ3dODd$m?89$rRwwggpwbAGmL^v16=-acXqd7jKa9 zN3kV`yi-Wv$gcTx9-qb=Orf+~vzA|eGDkMBFdUUKZ+FCO?X+F&56Ftnq`q8O>mKmg z;k&xK)1UD?j7LxeL75Q1x(4#u( zM~;+$^_@n|AwxS{8w3B_)%_B!7i8<5JlQz>Am#qLoue#ja*1ZkOw15A;D^L z`d&>rn+Y3pmI>OB9&^&|=>$uDE=z;c6bqMAX^*yERZvHQ=PVJ<>EP{UP+0C2TI{z= zy6t0k`L0hpv0jeMt8Pv3tcK+3nT63-)8iy$T`-#S?2H!bf_L3NM6UD*Hm=J;t%UUn z;_`v^(`H@_rY?{AFn_$&&kUY8uim?n=>PWGeNCpt#pJFYJGRa)aX7-D8qFKqe*5Lz ze^a$^xw7Z=Y2G%F#HFK+6m67Ohx*BJ_Uc79oU|cdq%mnDPDkpI2BrWE)jk=MtQxl? zoW#hJCr2U_zbB9P+{}W!DjE3sJKr^$cm_WTvY7&u zH`66Itg@#%iDSm^IiF9PQ7P7_ROmr6=60Kc;~d=NnDNo&?YpK=eh_%1(;MnHxz$5t zMm4!7$lrH{h2DTH;4?CBPSM~ub--=s$*AP!$1bu# zgV8Ou+Zrf3Qepn~3BIBb;=vF2hc`WS-FEoooeK8^P-MoFF(bt(OZ*jUD{+F+ z0;_hQ91=SP!5lVyE)s7(`Bx}@<_dXAQyZANzk-Mu6bU{s+vCJnes4@~IQt^RvV6gP za)58bVSHxFacf+9G^}mJ-F9YePpcAqJ`?g|HB3(f7pb4dk~spCa6ry>23wvEMYWz= zy2N)PrU5aKn!w~o_N(tAZI!29mjreppLh79By=5ucTcAO7a_TmzcNKgwN z`-mgpSJ*G_he(>A?suyr>GD0#sfBXivwt`LglBDI`I)L?%2ZJ%M~pguW)9#%Tx(27 zY`e!-be*O)@UTBEUf7}brNcxNqeehxL!vKmy5~M9f&QD)LcRoTVP#rMS73Aw_-Hgmpzz1ajF}_NZKKOI)m-))MFDJE}%m zXX!WqQ^%mf0X_Cw*zi!IU;@IE1~QL?y+0r>Kj(+*|xbSbfbL7E2tdPV` z5FytzWk1qhx#l$nnyh;w2CLSNqnR8SsUX}3` z$KGoD@SOJY{Z&VIuIUIUROR9{u>!LluD80K(Wg?0c7bCqW1+eoNFlCRvU83ZbADf_=cREtu)hx%+33D*b`sG z)8}S<2z%`9^M%{{H|E>6gq!g|$)u#I*U+0NLE3hRH_XEdxh zimK5{ZWT}?ZV_AXjMUJSqR(@>1{5i^Mt$9!{ard^LpXuPCyF#8$hTu05r}gfkFFCY zaIQ{XngRrmf*IO`-!x|Iu}cbd8~$`Sc-z7;w_sH?_%*$UrBj>0jG?Br?JjFE8g=NM z9tMb?B67{N5eWXy$b6&xZu38LVr$K%@ndT9oT zpP{PB&2gazqKLrjZ!p*99c|v@;=46#bnUCoBJb>cp9SHn0g0Vb8{yUkNfPXn;+2z; znf3EfnN>jI@Z=5k!6x#u8K=!u#m`S^AByTT+SbACE0}k-RquI!|M%Bkg9hTs=jX{l z^m(T?c;Dam{5zL#A68YSIb$8<$;<@0S(%I;q@o^?qw!TA2K)T}n?ybT8O8L3+|6n@ zvc|a*wd+1wtwbgR6-^vL@}r6ie_FBJq*F{moJF>1vB&+ubQiBR%r{;eYRBN8XNPyf zO-~!^{O2`uj^qDqBFG3aMDfLJeanNGt>p8Y(6){hW2-}+y`D4qXSCrb`fbO~MK{lT zQ#Y)I#za=6vIzB`ck41k-0Y&|>KATMo_!>Eo_HS31W!A_EHp45N_ggtgIkcETrg`o z)k@8=cQy@w9D(=)8aU>^!<7 zdhXskp;+)I!))r%gE4JDdJ&DB>qcRaMy%>ST#TZ@)OsXlpqz**rELqi(?Q0QQhX4i z+@~|k;*G1+ytl zc{7GFh`801xHBo1FdP?y4UXzBj~V+qXXzY%`wFw!(cp%|hoLjIrZ(>b^sVM9LA_~f zJ&KZ)Y29jiobhriPMHW7hfT!((WA2Ij`xkt46B04@V-FBt)*;@iL7W~GbR48&s^nL4N$Wr%S4oh}N;EpU zPiN)q{O0j4kf9-QGrHfZ>akD(jNkwM&p#L^iKd<%mGP_|GKs#K$ySi6%Q`3Oxzvg| zlBf2c&h{|+HzET+Vv>Tt`ciAw9?oH>+o3o>X<;=_v$Bzbr!ntL zFbXF`ExU zJ292-6H7gt-gzoEH;j%f_h(`u&IwQ4G(Omlxh+?Vl9_6{=t_}b=!Jf^P_DFy*WeCj zqNg^ktGJGx+U}?_<20sMoLZq(7cYb`XUfiVagOS=6ZJ%V*al5b7MB*SJ->pj3>()L zVP?;;HW&wk1!`ZDn5DVR0&RdqGh&{W4l1uU+}{dF%D2aams z0Zo)qp^)SG2T2|+5ogai59(IoQV9%lRec3>%Vvk~p+6BauJYb9{NVAkK`W+2ZTi=V z@OXasKzM?$aa|H*=%0K}^F6Gm6j%iU zD1gc4$w2p8l|!ZnqrpT5?WOW}&hD%t4w>Q?&TuR9bVo)4@B5M+Q7K(?X2uhz`1_xK z{?V@*d3Y2$I!k`){{gLd7n1~+Kg1(zP?^a;3b0L1+QI@3NKCh*F~_4fS#4tfKr*yr zOva4K%0n*Rh*XIkKH~pQlZUkmM&NJB=r|4O4#1p`*fo=oQ7IEwL*uYSp4J~Ys<9+w zNGY}nVePTEr?B^3UYWP(2691<2!I$EkKJonu{7fo_X+`aB;};SMniSUdFAUHsd;&=0YUA#+e7>j3ga+0xey%i@#_v8+@XuAr-f}pV4HV4ezll0nv{w<}tJVs4oAhX&CmQ{G5SV9}BOzt0wLgv>MIrfBtM(ztct(17 zFmkZ4rKRz~xi1TFDx`)P2`2Gv@iRjAwz3HpynR##hgXa+@6k#mWOp&eqZw%?#iCe3 z`V`+tjGSW)8hu`t?HUtQC`xLIh{?2rr5xw@bBoW|u5X(#O;%XyvYBa#MS_y!yMc<| z@gDAEC*#(E*Yq383&>y}?GZFnm?3?eq%Y;I6OgpU!GM7@BN$Tsh)SA4|!e1px8L8Y9rT#9aYa#ngl1Rwmwz_=Lv?Vq2D|0ekcYD9K%dQu+d>x^i$>z=Ei z`YZ>AOBSVb94C_KZ}szd*i5t}|2KDYwC}iSJ)4BAtUUHa+xwb3GS6sq_zAqZdN(VP zjz?4Uvy=Gf=tkRv6={E!Jd^CKYdaEE1U_sEhr+D_nY$u9aYCyMHRs__+0VzaF_-hd zj>DGQeD#|Sxj(a3h8I1(Tb&Wr?1EG>3>cL;o3PLlK#@zEsIK5>+Y(ycW81Hy9DC0` zd$XWDFdeV*{1X$nby^{%$1~~K?WfreeH5Si%}dZ9a5e^TCW@*;ex2%CDH$ilO1;*a z`48j7<~J(0&5Lqp2sgZDhz8gTs$} z1(C_LezG?xuC0r9TzmL)Qy3M@2QsIz$@#-UC_39}w*|u^IoN^kss|B|xdH1kJs+?i z*A$%!qztk@LJ!z2nbQmS8|>pZC}Ok}x+lB?ISPiIR+jC>0zT=A_BJJ7@QrHzzR}2< zFym5g+?baAUZvNtyIOr#T>;?97_|8LH}ea?CG? z9yd$#sj_s3m{Nu;N1$gP}==m$VYzrRls=U3AM0G)CK&Da2EuW;)oRysS&Kr`yk z%Y8Klptz4&;%ctihPrTM>+a;&=Djl1Nba5#q`;^c#imLhK_XU%znh&guY#~3Hex}+ z_Z`1-Qo;^oauv57ajrUd91W3?&)|+Xc}UJerM`cMSK&Yrgi{VZF*LAj*;Kjdk%+b+ zXv56Z;fK%|XW&oH2A9?e@l)}X_h!W1)OwfD&dQdt$3)un*RjoegCipgNx;@vs`$_a zPhu36C;E^^Sl{p)ZRAkaSglqlR`A#nuXLOJ`ai_=H0{OqNbZ3s<|Ai~U=ZhOh)C3^ z!OuRlod4>Xlg(SHL#}IP6bejzn7Ysny6)CT)Peem{#@=t2jQmj4md-{rpFM+t9~W# zOaRCI*bPj!)-l1sq3`8_FFWlolPNyF?k0}@bJcqZb%q1@O4Xwf%e=chu+-45yTejbbGcw)4vXnq;YjsGo#V&QH@X^u@5>FiDXzk;w~iZ zIh`H*tG4AFZ8G22kIFpZxZ%vl5!Qk|5UUrsuSqKSrx|ZVzjIA<%~Ml5OUSUX5$|pn zoHiE*U*A;X2?|!yy<|RX9pah}EIg4>3-qZnHtbO;9*<3D06H_<-1E6r6iMrL3~fHb z3a4qw-^yfW0G)PSx$)DEIyW*#4fCs9ah~@BFz-pIx#&kguTOlqJOk5;{5UCeIXZ(Z zuW^0;^uv#2Z=^tma*H?F$P!3GM&pv9!^;5&j&yY6gh7(vP#DLP82Ai#B6uK4;h-FN zjn*O%DO&{L!E_&MOT&)8FG0hC=W^5yaLvP0zLw20uagneH+%FhIJ`;hK#H%P!)!SU zC2M)>`|upH2@t9H%25x#nL^ah3xF~mrhr0p=2NKZh|ChXwgXY|#uUfwK=lL^uriPjfK=!HredhUp)k=R|&1s5ETe*%vlml1BQ?W&X^!AMzK zH@p1$hvN_LWTnMdV*DAMW^z)6#J>%tJE(O`!djzI3JzLe@NQbG&Q8@S zed8hxVItRD0O@ij9Z!<<9vzopF+Tou){d|bmOyjj>ZaND<+mRkwuo`jQwi;yXF_3^ zH9h7?+})HlDqK42P1c=n`UNP4{=xti7-(FEVG?LR!vGJmXQMu3GE#D5u=y zbA4qF&EmS#poE)|m+Uxp)LZRrk6196Hy-`rglVYZS*C#ikwtU=A#rRD1D1T}aXyPc z8SO9$jKK>xZ2}ogl2tWe&&iz}+?dqM(#M4IjuTZm`NwO{?|!unP=Y^qm->;kR|Mxuk{QLvP+cJtP(*ifeqw_im>D|xAo2ve&cF5gVw0BQeSZoH% zL$1p4BLe=UN)Cuf0=5qlS>DJ*2D7gnU^V%uKp}Dn8YHgaVuKv|<8GStwKd1iUmBj7 zuH`zu-X{YEBX`(!xwiynN~?c3*M&>Uz!Av!Nms@F$2C3H3@3bWeW9Y4E8YW}@w;UH zbR+#qcDu7MrH%zwMypfhLlY9(V8fyiG0?J%-JO-ysxvXEJA)=B1-B@FIDC}itS$GoBx;Sun$#0Z`kYT&|2Ewj zB7y%CTz@Wl-L9gWZK-_^Js6_C!8;7i%Sd-ljM9?wo8$9YsO3$dUHQRUnW8UiAiUE)yw)?|Ppsq4 zeXi7jk%F9_#2(CxqbsSc~J6io9mjv1HikEJ#F4MotQ9V#6(O zdGm+NI?rg`$9}kFX%rSop4gIUGe>fCYKvvEr}5B7&`K5DeSM&6xWEykUS`O`-b8HQ z7h1Wy47N_&A)tO}^{o*LYffFNf;cGZ!oQtGI&@rAd|+~Ht$gG=n2^ufej7IlH9yl{ zU`L@`E5R3N2P;q|D`9^&C7+nJn{-3xr%cJX>h#1Xw`q6aLHb!*_4Sg&h8!^|2onT3 zb=1UeMnq;ID>axnLFTCD=L*EAyW7H;(VKK3Z_ZRwm~0BAC7w9|Ov|ca6T8Qy7986c zGm)9+NLkkoHmlhY%`v#l8ye3-v%VrSC%$Mt;#Uq_T*Lz zPkf^Kb3c4@ZaOqoN58c_qs5{auw3i0B9SPU3;&FJ%ZWIF!{{j=g*O|n%nqLepXl1D z#iwn?#VWhYJTBmlg)~l9{8uiij&e45*YyY!iE5*!J4--7JK$LoR*;a%|#G|x$Y zaQA(sYlD6H37qC~R!Fl1wuxo${MdOpxqX6bh{Y`$L9r!zbAgv)2!!$sZ2pt)H+2OHyS|(+?Tm&zOMRWd`aSxNqrmq~`7=0X;9p!H6x*?u z7C!W3hP7_lvNh;spq%!Zj$Uj9#xU^IRqWgc#=-OolyojmV*dP_hB+zlBOTOcU<$K` zGvkkpEII#OkxUm4!Z9k&#M?~Gv?V^gJ=-&3lP#7fv8e@MB2n2f!~v#;U)FI>=%Su5 zYjcFWBN&s4kTV5Ga-x|skEiB5XW@Ut!8(EN{{8*!^A@VK8qR!vf-SSJ0;F=8DVP}q z1shD!Z$YtglSs;0eKI2Rc{}D>DBul!+t_Jqiz2_&cd9c1zQ29J`g#ig4P zuwB+5j-cwMR@#k%ZT_9#mexc}ksSuhuTySbgtDyE?p*GTK*Yy=u@Jp$nqSBQ#6+U+6!d;F|4$RVC-`Q3A)*6IeQ$Ee*=&b#-%dob9RoQO%Hx`9}XW%($j`5QH~zDM}K z!cHlpgM1@iFMK{}tJjafKkxXAHk5psftq*VgUg<;tlXMLz|5!dZkJ+&V_CS}@lGo^ z8-)QUv_eB@A20lIttM`hOs2fkmE=`w0-qI|hE`uI>|~fqSItj;?C5jAnBlJxFpGIp zkC#y?dzi6_m>@QI(L`ML%H*Bm7#0fX?J)X?ewVFgEw-#&0`s{SPdg7lK(+PW z(9y+aM77F04QO%_#-SrGeF~`3>aBj_M*dI+NUWlacebrc^TkFNs)Yud$R9lAY!6i; zOnnHS)CUc>R*nBjc}3ufovO`|+7bW0(H&33TKnSMP}re>sa5c#_CSQpEwtu?ja7XU zjav}!0Cn2qi-4K`OUtT zb3did6X#PYWU?4oUaPOw;wT7fU!uI|g-XRjijFpVabL#MLyC<*K<`{$)DV6xc5Z!- zGj^QRXaV7PP4%-YNGMk}E5`pn2b!ZS{8X*lZsF3{6kagnlEz2khJo z6pcK=U9lQG6OSwV5E#OppN2$Lb{dPJ6@~b1$`@y~?J-wu&8L+C;%xYY3n26zI-F0! z+&Oj_;Ucw1A@Y=a1I9TjsOPd4eu-w5?q-#iSInpS#JHvMneD01r4NAj%hX-QvH;`78k{uUr9}4Im))2G}o;832ZAOO)EfTR{Dq;a5dP*0S%=L`-gov<1 z*VeD`8U5K_B2>x6dPno~Ei~|%daMH~A6MR7^Os;0OW5Ao@s%^gURxTM;Iqb@PH)be z)BMw3YKgd(cyF;>T`Y|<{2=A|%Ar)CCs2=cZRgd?SfTeKe0^IJco%(KpVwl^k}D6~ ztgCPK3ospvIzM0>98Z!av85I&z`O`pt zz+k_>!HTR8Fzh2A(Ejkwm>>tiv3#uc7p)nMujH?D5&e5cwvA0|Yg0(WlaBq_@=>o< z_{2u6SIbPfnNn#Pj{CaZdFxR*u^P_-a}L3De>N$2-PBDMO;tRl$w)BzOkN2uh7+^cK80{su=ex;4R!$Y%nC*NReB7^PkWF~dU_im`Lf#M=SHgVL9> zGJIT?K}SKK<&b*6?MYI5>@)^T!(3kJlUarq*Me=y5^WD2=-r;28cHhAl!>Sx>r+zE zDl|wEd@xpMW#-Kycs5AnJm`bF(!Dpx5oN>heTgJvN-R16&8*R`d>F}|B~bPhice$; z#H^jOh{3Z2$nl-4L>NDD&wmSXUNX+h(tPOjdkQ%O#s)IcZ!_WIy*w<)io6RYMV2k-LOvf<>Gn#hK^cslrMXE4TRjve zKWvfrboQ$-3F8S_0UnIL{MbBD87CAJG?~nE$L(EsHV38Wm651hAz1wpx!D|&hy>q; z2$oV3;*W>X>)}eNo>qIW^vl?!I%P8m!xlH!btqP$k5oNet?CG;mxiUx0Bq3l z5WzLd^{qcpD|KS61x}9=@}s1DRFzzGqf-iNcdS=e)xQ(~9ZslQ|7BGl#AheHthQx&;Vy^LdJWre`KP zk?}l-5tZ+G$8(R4q|cCGd+{5v#RL$=Z3W|*!J~*dfXq0CKm8ud@IKugl+=k$wbkX~ z!5sY-47zq>>O6zFLP{xJTS-4!QGzQ3SsltQGurN>FdHWDiS3=#5P%td0>^0?5pUo0 ziwD{pNC?`WdQJe?`-|FEAd-AThLrd`>PUidlwLl#t2rmF^plqlPkxw!PCM6MGz;hP z>T-UM>2+ob*6B&rVw1+U}j?$*XdvlluX!Ho3_sI|)Qw;XsBDK|9485G846e-Pa z$6HH)xh?w4aH-Oq_i+A5Jq;TtQU3_DGLxWzd+{pPE+Pn)bnzpp&5tU6G6>Tdkz!~1 z&v{!b)6__7HadZvjWH_klh7&wF=iF(u~$CohG+Il-WG-@Vj_SQyG(MnX*atB$*dW&76uq#Orhkp0mH%Ypn*YelJ5bZ>mbLNTTIpZR$X6f!R151U z&J5NcadmsQFS8EJ1o8AOul54Sc>9_-G!{;3iby=3B&qxe=JZ@%ifH1~D?612K~SaO z8Bw6aV&%)r4aT0OoZ^`_HfhFMBOGY|Y5h)0@VH&3j@4j`yGo&`_;)*~S7|5-$R9Fw zOf9okAom$Px6L>a@ys?zyZnI^W^hQ`3+G$^yS(MRGm%(csYrZLS%W6mQV)R^F+5Z8 zlZc!w6@5g0L1q(@5t*q~ixbbp@8{|=8;E|pe>)Rj1vE+ADsT?w?N0 za`eKun9e4|+$egla6Rf1REc5H?Kx(R4DDD`N^AIG;OU}yIRG}@@f^A?KRQc zmbLqI=0Y1FVhwk#VROgKK01_ea|OmX(eakE^c*@v^hJEZydS+RGAdP4-`fH)QM!|u zrR90L=b^s3c~8Y7HD5+B^}ZyFab9^HyXYW0&Q{a7Myrif*`R1=;-` z9`98KSdHu4R*(rlDU`6HI5sJI1mkI8i>Ab3qXOq&LE?_{mqxRjf-!|PdcD3MCH~d;J81gi~X~zc7C~+*ZHEZ>J7dQ_B)!XUl-N~j5p6fJy#*Z zjddCKX_-^N4Uv$UWeU1uBUXHp8y?U=H<{jvXNk10R$^q2ISl@n_kVD4@};^m!ALy>;1s*sB}1d8KbzFoQNV;jA&W)} zxeR2NNtD6h?R;Gc;`;Ka?)+j^*1S`g$UDqh9v<9dXMG(08uTayXFtrFGG$o68t!Fz zsC!IZ(XUsKqqZC4>ur^7S)QX+F;2KY1MH;J^-1zW9F_y+@=g^hAq9k6iK#Y|v^mqD z{#1V9amdzA=k@uu&YTBdI`ZvRX5#O$ky!C_L5-!H^-0{2Mp!UmeGy3>PP1fm;ASgF z$2E~&F3I}m{IOrP1vFYkDq3=t0?AvDD4Y3X(or+~;izjAB(x^AjDk{H8{9Ie0x|kPW)jZ0le`}ar5n=m4#NuP+8xzimclwg!x7pre(=H0sXf+YN;E zL^5a52i9hMjV?S(qpZO&uGLMwJbiZPbIg4B{0D)K6LFwP-sZyH`1NDNRG9YOn{iKt zEPsM(JahF7X7=C`gWcGe&34*|GpSt6ihyOul3*{AqXK$ilGiB}v5_8aUUBNbxJI+JCaaJl}2eS63h{>FIXbd42U(8sA9O z^rlryq#wJ=A5l9mUe*(z6Jzp= zf^ipm-~jwEcapS3I7WAy#ec=9dhpL=Xlcq_DIU`I_1qSv5&3{z8^UL~XEq_$yQtfg zml-n$<~;L-2Oe^zNQ8~cCYYnX58tiVukOsKg?Wnl{)hANv_aE;o{eMG`H78N_s-W{ zqQ-dwNSqM{&ulAqvevubo?{u|bhJBQ9bk`!I>@{9b_FaI->?!D^OnKC;!^YX@6-%J zO3Q4L_*h*WE#CcUa)gkI8pra|Ttxklz=e0wj0Mu_q-(uF;0LX5B%yLj?MCA#HglN; zvtXlq3a-tIQ%$C*RNheG!%CQqwC~MIOSt+Hl6F#o{xs4)X^-oE@kB<5ZXV<&HKdMT zZID#Eo4DqA_I%V(L%g-e8fb49n^Ant z)pOsMGY&#|+UXW?tAbT{#-?u^Cy+t^)#!nn(Tz%Hy>pFVs)zjN$TT!3PiaZuRb$-n zE%qPStRAAg$JMphs^8W#L;f|f``c;Do$w*sNmomJmA6+NdMnGo48#bx%BN;W#(uQxqwwTr&s zv8<> z^J8|*csvwH8D&5In&GOJuvh(g4tH#{zzDb#+cpwCd2))c*GBEb$hg>}dEgFvylfQb zNJ838E*LSIc1e4NzR!G1?Y5ZP3UcaMHu zWj7Xk6YLg9O%-$L<@q?8poF0bZXFZUqIwqIHs1eKfR5FP+SexoXR`n$p?nz!(XZ1f zZF&KD+M55mXghA7nX*R?f5`g{WW?lUVID!Vviz;-?QI*nqN5wlI2j~gLuhnOt_`vS zN$mikfTXiIAD$StcgcS;yG_u&*cSa0-@%~wuj{vWAES0uoPhG8hBViR)@J?hi)5lac+Qm`oJJjkwrm&di}t2U61&v|1*1yhpG_+Ke}g zTX>4RYM%`kjV*;&5YEx!_}Cf~OF9~%Aq~Q4!rQEfq|$m?zqqssx$1 z2%{x{Gw~5Gb-yp3k?dI`gY~3Gv+O=5>JEE~h&j^vo|$HkUwpsdU-uQE0hQZ80i7>bgbLblhOhA6PXKEcC&+2@nW5P}*+ zq;OwlqT{^026ih#a0pCoRVU~rn}qa%%W?~ihrGWsC3u56B=UUjc20ymA5&Yi4;X14 z&Rp4^Lp$aT?v9D#oDbw!;dUgrpwR|gv+<~VAX3+T&aX7)Xr7}e!Ti8%K1ArW?YT`f ziJqCm)Q;RWGM(jMBK=w|7M%sZeIT3v#(AS>(m_?qYOxGLlcv25-JS8A3s&isLR{T> z#`**|Fbhf$!A3tpTLLy&Z~7hq1Hk;Gz9YME>Tv^EL^!&o|TYFJ{41`Z%b&G4hVXL)~8TFJz77k`yk_T zcA<@oM-QLuj7gAL&1qQWS{r5=cUTbKBv0hW3ZrJ^g%RR~dZjQZA>3PTy*J=`L}Ve0 zbMLfeQ<*#65DvgVX?F*_Izgl<2ejHQFb?pa9prs-jMogTm1+XXO6gOsW^f$JP*v@* z__)1T&p*iQbEa?4A_#6IccLeQA%i@MAy91^o#rh`ml!R#Gk$f7?37{2GN#L}Z9PnU-=1A%A2~LXRaqr@gJs$g) z9+|{7#+Ek5;m+@>hAe@XWTG|uKgaojuB#kpIEkV>wB_k;GCbjOR(r^ux!TsvG>y(+E>smTrEGL*pvEQvzeVROa zUaokTqUHSE%+4q1xU5Ub^ze43p`qoQsMCgUBY-g91$^7>@m%U|*Zg)gG&dyAEQL$YluI1w%J#?I00=JRleImY&5*)q}Xn!&i z3I?JwyBFfbr{nnB|2=`KIHdL$r_jiio2hlBsH+u?Q-$bU2*f$()F=N_StJ_0aM4h(kft#099<9sm#KHsEK4g3f= zL>W<3kj6zq_{?=OC-+8QSSbpdQhNh>3r%P$CzfP+Y=m=~se`kcK^ijS_Dd8D#qJX2SS1)$^O1k2@ z3}$bNVUwfSRBlioojLsdekVv1w`O{r5(d#}5GK755ZG87FcW@Ahn?~Z(lpq}Ua>~! zLd+1ixLcx2DAq`^ll&A^Go{v&GQY&t$V|u$pUCg)P9+K5u&#E{F-SO5$WWa~4LiGR zfw`{5@Gh)w=q`m3M>H3Ka9H4S#>#{_(z;?97zs^*r=aJlIQ)*IIYSHDO6lp3w&=x} zrO%J+!A&s{{A6v(9;IdtprWVtS;OCF!k#?Ht-l!Y(# z9?0%b81Qnd&zlHO_+oVI75!a{vN&xMHRQw^$rgAWEwP%T{t`&g4Ol#B6G9c5hh zq%&f0o_PUC|M^rfxU1>qCbrCSFnTmWq^*<%^~|@+Vdz z{KWS3X`(i1c}~GAsRt9yeSSp*h!lxHShe)TWWp(lhK{J#`twA@!rBxtMj`5ZmJY{0>vgK{8J?B0198sr}}uURJdTX z?#^c!e!l3#{z4@yk%($3$m*BG$GEM*6K!T}fjc zWlA5<7Ze}RNh4MFWGR8;LXs-D8YWt(ziKZGS9)TS>hY=rYk3aLnl*~JGp2`3^Jzp*slPPw>- zFl@~L!3IWy=ZenA9l13MJ!ck;m({c#!FtD6{opHCU_RJIZQ0SoYkzT8|9hej{DM4t`M(J2FgVM z?dZ|c?X0&`%q+b_-*5n$)KXGu6H`v-n54nGb{(Qd( z)TABym!m)Oi#VKpwieVY$fsLkg7{Il+{xfGxazw-wv4cmHQK>U0Tc?xrL zNMp7GiKP{oYJYw+pT5E_;R?J@wsM{&#d4*q_mnD(RHM73?0|C=5TrH2v9g|Au4SPh zu8WG5P%f$6wFzldD@8@9El%`z_u|L12)N9(e*Vc*95XOLf}LI+Q}nf9IV+WQfxtQS zvwzZV2!d?Uu`+WDxn^Pn136O`j$9pql`Hljhi^xTh(1u#5_kzzAwtc3a@w2OQg3z< z%Cgp}sL22P=l_;fqqG>23B?;RdjyYDC9}bUa+2$L(Kz_xOo|6T78XxMtCYn&8@F4` z8Hcrdj)E&EjEF>~84DUIheNNev0WGmx#E2DI%2{=OH6LIS$d1V!x;;@W9JIAVJBi3 z?kfdlfSf^K=3htGL6wsR5NPZe+$dJwR9-%W*V+I)P&O5c ztnB#{)1ZgO+8nte-i&wKU!D00hnM!~r*K7!UdVi1Y_-eqxz&3B$ksDY%Y4k_f;FH2oIryA-&N_CFkX#cbgXQ3I2V}Dj67O>eZoTr5r*m!u_5xlOoY|!Ik$nO~UARwiQbQs21KIW7#=s4=dk&3%Hb>p;n zl^UJXe;1ZfNXx!ChvhpsV|?9JWfN*<&t`I1Ia*-ZeUS7Z_nnQ_C^0Mgd&HIPJB9Q8 z?{P;*bO{NBp=2R;#?J$A2IqR-#B}%XZ!6xygjyK{I`G}S8v2rPWs5b(Jp()Yk=}!_ z4C!>}-AHkpIHg_fc>yPJlMJ5`CI~tVmDMA4yKGc79O0px3p*W28y6cvB;;^>#*=u3 z^o#=-UDfe?7FYP4L!ftM`yJu>sEf<-G>yA7uW>JxpQ_%Nh~IqRJM^J8Z58LDiwSuS zL`LO!dY4<_qyv$if9ozgoRfD%Cvss~RY9Thl>CvIx}PjEExs*v9B7o?C&AMQvx^X| za%E<&jG5omch1MXa|H^r(AgODNwhO#m-P?ms?ZGhg2HhFzIf}L#$qQyVByVuWx~+2A$Puaan~8Y0(_Bu}30i zaCWfj8S^`W_3x=r0jvo{`;6>n^S3#n|v9<*L*?QO1xGYn~Rc z1ssS-SDy$hAv({mXUzGC13%8}A<5a}mr#;WNt`>TAm|K;9f;-__yK0Bq3{)z$fdNx zGzI!JUUOr>5wE{CeQ7tvD&!vZ<#YUep<9Zf<{BRJCPoA6$E4`E61y%c*(dAxQf&ku zu7@6n8`Rzjr%_3fSWYHTeui-ZFrfdG&2>@YS>A#3h;!{UeI*(PPtYm0l5qGKq8*zpuL|av%J9Bfb(Osvi zJA*%}ZuL&{b&%*&r}~^z3Ji{QA{zfa6{q5B9PTbp7enU#xZaz5zO(FbRuJB`;oC3S z@7hnQ^E-l4(QI-R5Pcwrc1Je|c+8B7UKp41pIGdb&sNQTs;IJb6xJspub<$n>ZwN>l3${GU$XH8);yPIMu; zZR0MeHT|4VRcFhLGuYMG6&de2%$RCg#Aj?exuy|avF4mWueKkhC zYo=`WaLglK0Tg&z{M8pf9 z>nE_(TM*0>ckyqh=OGODy8J<5`9l%bjVE&mV^i;?km_#MY0vwGr_50QQq6BrItx4& zA-i*{?`n>PWxT{e4Z6fhR|;`jR2Zl%+n^+UrJT%Un`pR2eQ zTsGD6>^fiOsR@1W4AL*U%8oEL0jc7UkdcWzOtBwWqyVm+_@6EpZ<$X7T3*83GX-~I ztAG$@y+o3$B6F?HxjADjACV~dO=G?H4F*45f+@# zXfsz;MMoW33UU$rk;(!%9yijK#^)FqEox8=7)-#FX>0u>)gZW3?uaR8(Qziq_vWB_ zFu$GwPuxVZh`Ue<>=gzVA=n-?9~4Ggx=j zr_*FU#`9hDOLx?n@6{lac=MJK3G7h#_3>N{(#UD2&CX%oy$k(&i2Sw)!cOR$%{auT za%10WekK%z)oMK)DG7=Yr~dp!SEE1Q*i>5mT#EA`&4kkC&vW|8=THnrzlxwX2^*m# zqVi|Z8Uc!uf*U_LQN8!?ugffzLdjO+`4}TEgcb%G4nSL%R?YSaW#{~)Sob`H^+4Uy z*||RM=iZI%V@fvhKe2=u83(m4VB1I7qjKxAD8OTq`*Z8qnRH=>cxIMwY8sUnRMf06 z`@z{K=^V@TykG2Bazq(k*2M#aqaM2&*^TP@syphOLy$or7ZABoT|i_mJ_Dz_x)WHL ztu91$XoxRJ(MbtTSB#R$#RYX_RaG6z#5}!2hSWYeM@l-7>2!5x#t-A51+tVksXG!& z1D|03o<;2#j4CC=pT{R9o&79ZdI)WB>M`|+O__LHFP}9vsUx%`nK;` zq+o=;**VkPx}j=1BXUV1G#FSriLL`mIfoR!^2uL41+j>FpB=3k9}A`nH8D$y1yo&= z!sG?ylgbDUGz9>=L!x1RKw>laTNX$u6pLT8q=cbHtU#aB^Q$35m4>DgB_UtR0p< zw|q}<=;V>`V=;z`k==ac7H+n%{H6V;c{cJlE_^by-f?cyX1NXKOXeeIxFLGLtENn<8>~ zNw{1C5HK;w7-fLXqz`}2^D;lIA0U5C8Q-!At zLT8D{M%RXBktd>#?|0ne7ZdLs&Mbuko^+3tKvzm4u8ch2=`I~jEV6QqkS#L#PSMHR zRONxnYdz7eiZ~WP-JX!sr&961(aqGQ04lPOsrxQLVMBFCe*P^0V6C-&{6}h$+{ycn z+)HG2*H_iwzyF^-L3uK=GSZ-*^py6cn;F@CPMl9CQND0L-zW6|b%uKh?=Y?aU?k}> zu=!2CvI?4fka@XzaBZYaOP&Q=WCfm(Fh{52hD@kvTWF^{uv0CM4m}g?Cl*2j5@IJx zW=`)*E1s5$ZNHBjD+=UfIjy6Xkx~A<&W9?@NudzGd+dIu1(cnS20s5JUT~BtiSN{@(}M=Bs_KrYI+?lDlHpVf zSY=R0Zr+zId~8={(wL$tXMELFRUNT%&%mPUD0z$myy#QSlRPulN}v-5^+na@?$W6p zUD}m!yH)Mnf<{%xv(*{kNSuCj0S3C9#>M|Dwvfk@c9RyhaxzRKjqo*#0ng&VS_1cR;J}vQGI;udvP$~OFOmK2Z6g5R- zsHX8)PE66QFc0~eV5#nrB9}A|?Zl>#ha(QNb|W%h&G$H1t;Xsbc$-7ac*1th41yhS zO_l>ctFp+>stviDS{*keJ40GY0};2F~PJt zY$%d%`zPml18c=jswq96>iDJ;n`|>&Er4dqC)$sC*+;$dCSpc6Jy7eRtCS<$!oj7o zWh+%;W(u-cxAPz(BkO7eh7GlX!6&Hz*EwZSdB7bgy z2UFU&8sYG$nAPXm%6HI>h1i?{5%=)O=-2{K;vNp~xG7VXV=ypjTt$+dCo-e=6JXZT z!w<}S&d)aqg|w7EQqAxx#pBDXmLD9PD^d;2$DL6k%b3k3YcV3$zACM919hH9agdrC zWSpb=7jE+q75Ki>MW!Q?$pqhtss`#5d3aH!uEYFkTK&83g_6F|dv%;b0t>lTWd^#t z2uzJ?R$+BcS7id0=fxp_rK3@C=D zj|XL$8_O7XY~^ADfrFp49D|%yE28r##V5Y!S+wZ_`gjt*yD)wS^+Y__uq)m13n6J{@Mj)H_MBz0F(RNeUOi#OZuw zZ$*H#6vDEEg(^!4|6CPi3j&mg^&@>(cI=FwH0O#c#g@7Wea{;P$wuEgtV7uzy#g6) zH>>vUK1Tyvl!nT87YZx75%pLBi3^fr4O1uHRUXVcK|e`j7Nyt2Rc5wXlK&GoyYen$ z7}91-wy?Y1r{bJA#|yD&Kr`c>BK-LxMKN9WekWG8!l))=g9lCa3}VLpG`6w)!T4UN zX5Yq-VG-M-R78>ph(~A-TAJ!I&fq?Fje0u!paY%%Y^+@LfC8~|oi9#xcO@NftT1^c zOIk&H7z5t3hQQYiWJ~KyV+;L49o_YN`1~bfh&hq0>OQ9rpG-t-EOZo3btRNC_x=9z z99V11nPsVg0(s5>*8cgYdi4i;ixDVd3t^w77<=g4V^0{sjw|@gSPO4*MtaA)T9Ju4 zzuIJ6b2h9V^x^Ju&R8hV;Q5j+hhDXxUbrKo>iFe0P3?FFm`rtm)4!`?CCBOz6D8zA z_t011Jxw7M9;FG6l5Cyj)35xDeKiS)P;Wzni7W{Z24Z%JXmmn}Dn>WUj+oI9+iCRH z9sxI6u#ZG+@%${s_Vkl~pJ-1;CX@r*%g$t)(6-qJqr?7iWx&MPz-XvCQJ;3*OxR{B zRc3VdM{Cxb_N`zc|Gnz{jG36ds6Of*=r8}_iXL&65@pezd3#2=ZtK;eCo0u~ANcoa zr9U{d>NsDZgf`S28Ug)ltz26cd=!OMpHJs!Rn+P1R!OkjiZ6pUQXDMV{KSm(kOjR~ z8yDPlELVw+jFV9(>`MeMFlpN8Tfrp#ym@-E^m-nT}?iS|2x~}MqkYVV$TnseIlwbIdf2&Ibyr5D&c8*cv#KK>+!fTN8PA78Ep`Q=Y(N$ zF=L3h`$Ns_V#o@EXO3JtJXc)VK9kZSvn!;yF)Tsz(VJ9++fB81)C&)H zJ-yJ%hUB2~^Ap;C5$MAqN1*AQjw+`|dBt)xxJCZ(ukN8U=FysABC@IsWUjyxVQ2Ev zRy;Gide=^@A)7L2%3zuCAO<@Ei-K`)4;+S;{5KC-m*R=u4m^PBSwg6ly@Y%}1_v{? zzIopg8uSF|jbM=4`-C9ZqWEwN&%ghLJYY&k zoakRqxyn)(zeR8pSk1AG%KcMjhq9GgeGg*egv+@>!lnht_hT6#YDIT+5qE}0pCBet z;GCBM;}GR?WMUWUNQx_v;3WCwjoDxXNF5B>5-s@|{>PU;(%*>4_(%6yvk#AbnNn+C zO+_5Bs*!<31*Mvbo(%UkzNK`0RNu~LA|>zU(^&`{iy8$1!{Sx@Ub75uDQhjFz|YyU zc}Xz1wQfVL5>e#b&`-Z)w^JTlMWP}*-9`dcebaPjUnl5<^uv7$_dSdo`3R=Zga_@I zd9({8v+4Mhc%U$Q9)sqm*ezO9svmMD04Jv0IEJc0gSJ&pfvs|(SXDh)0&4|>^01^K zvC*+V`fMK?9-E@Dd{NtqdRV*m0CY(zf23vV2aT=qXHN=lfz4|(_p64WKxa4@PQbvT z1?0{u2m~KPpVGtTH4ztp5SH-74&BfKIIYm=vZKGz&?;9ChTl%G`-D|Y`lUoAG*i7< z%EvD}jl}F99LebTa@{e64xCgVASx<=1wQAes;ZJ1yS);p>zr1uX;6sNd+)t=Dxrj} zWuES}76RvdjTDXujIR4Qe8gf$oN|advAC*MZf%ILxXsDV1ObI!t|Z!AKMUPb8PA07 zcKlZV=fD3ILa!~?pUmY{3J!8L)dY%iskX07L-F>RJh6?7YulqEB{P5Me@i5dYc=Q= zuu{*vYmKipmZe9-lKU^4cmnsIwZS1aNOqvZ9{pxSQj2bJWqRU$4=i%v|J7_)@df7nJ# zmgpFqya+cE*Oua?gm9yiP$g&girmrZ9ggRp;mzu79^lWpXGW0s*5+P!uj%Ws7Jnwq zJG2T*QvG^npcX#NjVVF9ru5EPK;NXF8*IyU{IOUjbct}5b#prd<4Dh?PPHwS${l+- zr4J}{^RZbW_B95D8GQhb&sMBf^av8=j3br`Yh9pDq(;neZ_h5b(We|jFVF^Z7C1mI zu3>Fj$_p_j^{&?LgX3g2@<+)XBYt8-%jw(86N$<4vtaP+$5p|1N4|+B>N0#(^y7LUo@#B5 zM>#}(vVwV>uGx--#&;#%Cpn8eO!QqZwOB&S@aB7rAw<3}D^@6_mQa7i4PheqsBZju zR1hYjW$#jw#Y_~_TU^*ugzRb?ueQ(E3T!aIPrKO@qjkT-g)1CXG5>5dBse{%&#~{R zM?C{u?j09@CO+l)NGl+~bvvRXz7LYAqN@#m9u&CCvoRP7sFpD&`1;9Qh?UJK zAT7X_kT%dfJE7A0~MaC)R4}+u-5>^Nxg%kQZDddYpA~Un9kSBklag_N}>0sDa2|{IESRu?r zAL#g!xM&-0EEhz8`=K7|k@HzLpL@(0nQ%f362h=hF_)x(TDGty|vHIP^$VKwrA;Q7)0`-a1goe6)IJ2CARaVRKk_&iGe>5)OA;-7Kc z1h_>P2$a?E#t<@lY=^Cz3$Io=UVarOm3@drc;iSfK3su&sOX~_Jtsrq0nt2u_kT6WOO#Ft4LlvwKQO=m)XN*odSGCBLv8$GC9v@ zXGC;tjiY5>O#Nm|(DhxQbuMpR56C0Cets4dvc%Q`D7d%e@-}aNjQj(Od}Hg!L;ynL zMgcQ0W(+OWrd16$*z#TFuUrb8$pUmK%)6KJOjcjR;rVWJm@77K-+?Zyfut2Yu&gnN zD;eU3bVuId3in!yt8j2%|MhCNLl??d*?6IeQML0@L+j{RX&M1L001BWNkla%3?Jw-O1pL^Yv$*5f#g9dJXEJS%yq^s&VD|=wn4N zA@4ibv@_a)v~BJ^ruk{FP*%p{M^+T)G0q-O`d>hRW!Jj;BJ`4T%oLp&o34_$02P&V zHipTyppLN+-SXvS+mDBhpP{%EXh{G(y`tdx1#3CSobm9D9qXr~ zQA4nf%pJ|xjoyhBJCUdRR7H0(Z5WWblHR%Y-cv;dkyf?#rerSt6`MES6z&oc>%3t3 zCje9*@%gJ(Zqmtl@+T`@0yDe13P{>W&Z(|G--nLw_0c{1Td^vFSI)ko2H}w>wkKjC z_I}`}XQbxHA023?q{ctJ7ELL z>&jjxL_9Ntw30K?^TiFSEfH(AkQwxazVUcD%W$)x91r{QAn_yyUxrgiS625Mlr76M z^CClZJk<^4LcRySE6%EmEY1=z^(ubfI>Gk)i%P~VtY!S`$1Ohtg)1Ps;#C_)CZhRb zKB;L}x$Bg>k5s2pa)(;h9cwk5ss+nuSexm~jTcxYjeSH^Wi*a|LRyP!Fi|~8+K9lR zlwjb=G0BmtALyFL^RWm~moXWC=}AZT2yvC(y=>C<=lKL#hX%W96m9o_c!A}q=gla> zjJ7?B9XNLNy~1+R6raGNQKVz+a?9_~^W}~oKK|x$NC?lxRJx&2=Wv;qrG*bDgp+QCnYceZua?m2v9O>pOGZIL z0cDG>T$hk?-SQ|HwV6=B$6cBkm@OPa^Qa?o~~xqT!Ik5p;$=$s(M7^+0QD3`==uW@JJPi0@pK)YKpJ zGW$;T;FAHGWi5)VGLS0foC}oe!>CQ>Q|07RUMi2WH*~(!zMLIP;Un5Sb6^jyd4ZKZ zaZR^iveUap-{i=mJv`@@2&sjFJynW}D0H9Fwk@>|9s)t)HZA_ibWm<^AKsVzZnW}9 zBPQvd8$kXRv*AgBXiM`XAC!*>Q$Lgatph@|UvbX9nTbC0uwSf$37F5qo8%Y&h z8@6$6NRD?`f_9t&0(MH>Lu)GM)5(1;;t z@~2n4*6^A5H#&%=i)L=yd$_rIco+@ZCb-S8qRsZHXRy+E0c_V=AFhLKQJ%cUG&N+3 zW?%b?uc+qAmseVNe=h6z13|TDx$gxt;b(uQ``YuQcj$zR5aJBtw3L024vpG-B=i zhrTY!)Vmx3<$P|*P7G|rqb%7WU2@t^W}Dw-_TW!KOg*I&B-f5(%0Kx!XK2qe87#-Z zdf|_ZTr*%*FPi;AiJoYyvGEVz*yU8>iZ%;k1H$s`YTPyB9})~7?~k*XD9)K@e&oa&K0gC2Wup5)bK*~Kl-@^KJ{Q9wZR*mt z;vu7sdqOd(>-BL-HCHV*y_PUU^Z`dv^2`g zmSMEG75AP~R$E}6T3nYYEQu^RMU@3gI~)hNC*pjF>aTjHwBap^(#7QwuoT#G+{^#C z8D0GZr9i*CEl3zo4edpE^M)?bSH7=Vv)cypoXc@euZ+d8z(3q<#)Mc&zf3g=o_iN%ptsR zQ9QhuuzhiPm(|Y43*~aUdyD>5xv{hA2NrG4JlJ?&)Jr*!XK*2{V?4a)klOx2oALNk zqS&+x@fyr#xiQG~M_6Hma$i88$I)ZKBc!IRDM*VY!-*mF$h)Bxk`rB^1t{i{Vm%f4FBJ?G2HUDY--K~s*UnS zhuSG=N95495R)VV9Hw>tk;Tyr$j-37CpKx`>svB#8^o>WlUU??dF36a&geD~=hN*_ zlIJ^Gd;~9y`^gKqT`S~a7b#rsH||9&}nAA=B_vkNouG7pczPXp}L|ASk+PA zFGI+Ynbfp0-ab02GgmP|IM&zQt;IZILDfU5UrC~QNmwpvmC;D-*xjci3YmS7JAtoH zIM$ncuXq~Bu0AiF-~jkpVB zpM(9QQ*5JSgMD;#A6J%$d-+KL<@5KPSP_wZj^fI=>hA0=HTEwH7gn@ZJqV&0E;RQE z`k8?%3ZQ&q`+xEBRcA^agdSpWukPm$U%y8XV2AowTIU{NnVdoD#Fw~%CCp<$R1^?j zM6SIRJy)fudfiy2+rz(rPIBr-yM(PYdnY zEg3WKN!+M(CXVuPs3R#0Uy#$?N4u#)Bz?vqMScH*eCF~aDPswDkIQxm`U9P+#~Y3V zf_?@l=T}ABaiX&194o#rDq>s8GK{O7)CZxM(GD0$p`?Hr*^WTkjJ=IM#}9lfNN&=Y z$D5Y`O+d20sOa45l8__$TXZ({n@T<9_zkyMazH#ae=eG@f-$*50hYE(Gw0H(@|CC! z`Ov$y=w3ST9!tQv&k|<=_jh@^=);5HQR1DRr znnod4+K-o+c?4fTBbQR?DzFv;!1sGnZn4NrWifz3PDtL45q&Ci!R(I?`p}k+_L}rJ)k-3q!nx}0Jq%G*_$Qv_8 zAkdK=fD*=7>#jcr4eH^V{&1`aG>!yTh4`$2rw?s8{CZMhY@eYmo6B| zUflk12qJw}Iwi7@h}g={;|*7b!+y+L?3uNPq_UVc=?|P+u^>R)tWlw-SL~GSh3AoC z$ujWC_L6>dYm`kBKf7Ydaux(Qfp?j|mt~Ve%r;4m#oNih_zCW!*DS;+Tz4b}e`c=X zS^ZRX7jiurt??j9f1(by+zPy86;WYGVW?Q0Fv8|72}_+Qbl?zW$E5I92St z;zPdUKpdA@ZHm1RTXkvAaZ1tvS)f59l0-mrI9Q^hH9$P=nE|#PSNH?qn^AcAO|kW9 z{=;g%s;vg8G_OkkhoGL_)9to6{|3x3gm>f3T5ZSmF~KOUu-yx!mW~~r(-!sl>Dg$h z2Fv1iWI9fGF{_KWmp-_t)m;(C+kHf4qcI;mPn8iCbYLsxq03|Q^w5gL!LU1B`!S8k zZfGBTF^$=o6gvLaEbiC?l>^n#H-A-KC%2J~4h9*bJ3tR-1QCg2$L<&@q94U&c~r!A zx+3ae?N~k6Tz4ba%_3TPBPXvcwlzVhGoeq?&1w^_w;+x^qFk=Hr)Y)kd8!x|g0;~t zG^9-2Hy|Duo=|Z(fKxnY*c`VtNt22}4{IDWOE_yrI2Y5?KTGWPh@C>7_TF1h%YH0) zKCg&;^YF%^tuuHhq&w2%jBC(wQ`hVoaz{4Ho+8W;$n5lFMRsUO`0m8BtB-}*iMyPT z5TLOU<;X9&duCudTt@5cW)A?5(tubI=fwI)1H`iZkar~+0V0qY6QhZ(l&bwTBHKwX z`uF(B2yl=M;Ad$njnWY9I{rk!k?JA+xQ-MJtD_X^(;ZmOK8_$PqiVtsFmuw_m_m0d zOz&Z07^e#<(`TLP(_QDBQ>V1rUOe>nRwl;8w{%B2j84S8({&0n_!rYTiVz`r7_ut5 zzCLHM361J5!81wKW!CIDpKb!aFemy{$KE+HM%*;#&RD5MMn^<1bcp@g5#K-m-s*5N zaeO8c(ZK}*M1yD!2t~o^^op(k(~O2-_%e_=wgqrOr&Rp zblO=y`RFmuJ;9|AW^*O0t$zAxd%XPzhi5o`iqi~RQneFfA6m~sph9-*X+}!P3|7ck z-B%IE(&)5SRv6kCkgvx7a9}KdW?qM%!UGL6sH=R;$+q?OwN-H0x-+0l;d!MY9Iajd zSsV6reVW=@=ErSDOn7g~+=%ovzcD}por@W3X&D>YC;Heyu_Q^-6+P;gI)TQI#h<)} z@ho<#Bh^Dsk2thI=bb2K_l8KxV(_NJmqrUfs^%`%CV zim2&2r@DZ(va60aqN9%9x%jDV+WwoLBdEBqiXbZ*+0j^u+|3Heg*i^#yuLra*haJ45H`2Jm(^ik_7Y6@s;=54oQOK-^uQ_XG@MG8Qd-hG!- z+Hg{fq!+hACd3!`rBucqd>v!xJfZfd2u2M#B%>{ajrvh zQGa*8`u0*=Yu*&R8frYcD*z%(WMeL0>jpJh1u$Vh>zQA>MemC2+{-GsAz#cq#JN^i zE*yWNK{+d_6CdB86J`haR5>q7{mG~4IIR^W4HkI7GYp`T$Hv`|u|++5n_Q9ESWSBoR?hB`xh?Kx&0Ro970wD%uRpVy$YBWxekW`%eSizr&SQtC;I8+rX zQH?nbF3j5ng=lI@9BJdw>KMSbga<4~giKl_-8$n2DhkI2>BK`|#oqTm{T!v%P9B;H zGmb=dqpZlfj*NKTpq2*Ek3?c8ewI$K)}Bq?NId9CuTQ%@Nw;`z6wV0Cu=_Hly4)wX z0JJxHB<(hjrHvxr<#>kIM6lCJs@1EfLe=sl#)Ih!yYpnuKMr+UdGVhqO>!5O!f#=5 zfXm2rclV`SkQn$)-=yE}ieJCQ%<)qC)Y0ct##RgcZ3m5(zh@lLwksdg_1fz14qo3~ zQ3s$RWpD$yzTa)}`Gjw2?a0t(a<)p3H&?PFMCiO+gO1x*M?b=)jODhuQpDImtkujz z5(|COEua0iW`BOy+N>-QX;LFKc37uL7qTJ`acY7PsYZ_Mm`~3^k@ElhQPfp(=$T4o z?hu1B5`c5!J2A<5q_(dZM3ZIOeL5oh4C%;R?R3`A_|4PH7&~|lU+jMbmQs)&LU}n;FlgFzC5tTE3 zIy|esb47i*77=4QnC6d_kDzXBU?}X74@n#;c{J}BeHN`#H7RPQ%+Vc9&~(++qQEd^ z>?fDptA9OCA*V|j4A6B^mU>ENQ;O%8MWEt*EF%hy+>$|7%P6k#66}{aT1r4FEKKJ6 zrx-fDT(dOLDD*sXM@!$AGY{72)qQ+4H*!b9(#+@vGXpYiC}q_%j;7dqU6J(hEKsOw z?K`4qGVe)Ey6MDF=z{M|NQ@yIuQcIKFM}elYF?SrLw?OOWMb81plbj@NB`grbk=_$ zOmeI*Oli_iDOG)Pyt33SO9>eqsHIO7pWQ2il7t=?_Z2M34m3jRzNQ^rW;2UX_G3K3 z_dUyVU8qB=K{gN8odLAuNsnHG0ZF13eUk^;UGe>*PIRo?2=wB{sgu`&V6P6(L8+WX5+c?dw`iEmw?aMnl)gL7r`}F zb9AD$6`V)Sv5`s0x2eAL(Uw=D6c(mgr&WlT${6Q?nR}F(V-@B=AN`elF>zsge@uX4 z7e~V^241Y(6E2(acseB>=sXLH)YT7%rk>y1;0dVHw+K_O8$uszl*#6&9@`tSD8_jR z2~nD4mH(yj%6_F%FP$ezUaQileCN$R_Q~q&Nk>CYkoXl#m)(_V@?s#)Xo=GU@M@=z z=*v1IFD!1S@rYDCgODr5`oDKu0G@j^~oXOe^{Po*1YUmN=qo$P7_XRh+3(HgkmmMXq1%~xUF(}o}L*m;?R3O zWL!kNYzvqV3Rb(w$n7KU3ZgnH`upy{=)PYqi84Q~rL7Db#h4U$UE=kG278)AOkH~Z zTc%8I#bLc?Z#?SUdh)#!xpdH9 zhjwFh%Y6gN$4KY}75D#jAp-r*Ix%Fn2~LxsL+wjuSzc89ki`(uIi|qjG@Qxf@<1!4 z3`#ms2asd&gAVh)l}Su4^mx~WNN{9vYx;RyQxvUgyeLLDdJW`WXVlI8GKO900%H;Z zdjS>`=gxYf?@V8jZ^zN9o*3qM-O`W%J|CUPWTi0qC& zhg8!UmM$`=o1&Ji_6sNPQU$l6%#C!6pFtaJq8b%d)x>+@RCIS%VQ)l#oJN>QknH3! zoIp-3&S3NsF*f=(3BoMCNkHKkjlbUSF}L6<^DoZNE@!Ep7ar2)2={#YWJYfP?PvIf zMtY6DTJ+Io*--`w)FawS7+GR zB-tk>aimJCheqp?Yx%wH``ZnfQ5zChXYE}gfBp8pMVd#G6BV}1FW1|Q!E6Y@j;`v} z_Na$b-ST15j8-DNx>KCjefjT?Fj80S~~foNlQI?HZ~X> z!Cnto-T0Qce5~$>wZtH$nG+uK>$URmRpF)uX0a9GG{I%sWLhC9rR<36%mV*^|2aQv zeT^1tDS;n4QPj$s8YuWP*J(v(N-7#q;^ptF{^+IW>&a(hA=iaWB9M));^eWa2$-tV zR{3@!PnAfz;+L(6+*k>qDk5uK-#!`6Kls1ViN#q{)ZrOHgHJ^Rh5a%V290YiO!qyt zxc_O~8Mzk)hKjQRH|_gbfLw0h=Vzmj&fujURn2Drk5!g)*r>b_ZZ-4C_Pi`^{_32r zWjaegHrXnEF{vMuKP>s*VN8?lgHA6F5rk0XCO8j{i1cOD=eyXXQEBNx^Amy* zAJ(7b1x&T){vj8L#nh>smrfTEdtJJ+q~`b8>vCg16xQnbcrj$)gz$!r2s2_Wp9x3= zQ@{zK6=!b#=A0q5qPiy+r%XFjlTgvWP6nLM%^91@6~R@~rpyWo_qZXf4*gmi5NmsCW-dpPpO~xR*tG zqCNTZW=4BtGv#S`pc(&5gCoOoHr^y6OFSl+{w{V58L`+}g0oyb56CR;8nhJ)z&;JaG;1Ij;I>(`F>`?$; ztiws}iug#M6@`ZcW7|HbYyeXdFsqu`zA_s>clmNWAweQJYjL!OdD4?sElt! zMgV*Ty6n1l^UTMr`$48hEcA%j*HsI8{*H^lsJc0nosHGkb<%OFhwxt=PnZ&&l`RO; z=t=Vqk1%RjX7A)ZdzrTy9*ekv()1@+7ozflD(brc;I3;4zQ;(!Q;_SxT+heOpoi#S z1>5u$#p>wx2ZaFV<@OvmM5LZe1O@Bib_iKm#+Qj zu5!7|iD9lC)g4Kq=}Kl=#n{U-f*^(AV6VN|P^VFBf~V}ve{x|lEK_wP`e(=bKmUqk z&uNM}E!9_A!K+uHTh>9CMmk$c&KVYB1%8$gb`#{oAT2>?g}{j_LDe+mHYZbX!uq3& zj(i5RT3xo-xfsLdO<1XNSX#t6m-hfCTR_rJ`BBfXUIvoponc<+_^b=0>+a#O&t+CJ zo6Yl!0-a{nPa<4icRM8zZ|D6s-b0j`(x*NS8~SEo%;$Vx8u#1gszyIO_M^_tm$uqn zrf?J^4>@X@k;SJ^qpQ!+f}5d41?>|weQDx_^cA;azLBBXEh02cat%k>j;QQT@&O~= z6<;oILi)bEN~jU8`~0du!)4VW%(;c#FN&u(Bp?bnLBZ^BfAzR7tbN4@dA~lTo4j~r z25&g?4VTk{yDpfj;%1;YCwVql3LAsEvB7fW09W16=AU$y%LaFj3>w|uX{~Pst`V>05(5S{&r@PPT3}iOtJG8PV5p1ojtg3U)mpd1gmRyA(tB;oq z@txBhrxXW2%aN+CD)JJ5KFSAzWUFU5G({A(6668owy~fnGOFt6UHS9>(VNQACb+db zv@@HR6$UaBiP3jArBLsSlkLljEag~WT{h;*h)O15F1yxY-V$*6x_koHLi+7m8)>pg zCt^Yc$rkfG`-W09GGm$JTGpn*IB1JaN-~gh(+&sqOi6-PSYCtAnZf*t{?$NT6TR|S zXL?kaFG7ud-?xU|hYtTbmW4~_8p0dG8w>!8wM@p{DLPqT-JmtcuVV&MS7hW9ru%75 zt|_3r2kOaPA0@^k&T?%f9F%i1FB~4;ydJl0FSS*LmnkxReV-%@l@_X60eQ4B=s1Y+ z+ZO^qH(=gKOr>odv9+<*wKVJgMQ5qe+=y*RX>Q~zLM~zk?vbl`Un-wwK=i@A=*8V% z^P!z45&I&(<>Q?y7*VM)bWcBjfx_aojI}}hE z0|OnO4!q}MydimiZ^4vZ%a4kpB%KboI~Zg%VTrZJWlFe zCr+ocf~q61aaR=F7UiDD&N-eD%j6D9X)~CT>ul}*_6Xu)(Crj~j;hnll=rQMsEBd>iQt~gj^oUSwcx28~VVK17~5Ose3o#a%(jWyaz{DcD}K8w?84rg@4f6r7% zJ@kU3HBK;VaRSHJlMd{OAQpAt8Fg%qjUY%VY*d>}KWH}GQB8yKO02C*V&R_Y-W_3r z0VK?;{yjIc$ejA-)-J62HiCT-u7fw@~7I-o4GJN7E76K&PP=fTk-wIcVeJy^|un` z2AlJu@0bjEG!+x8fGxtB^hp0w=<)&X1c&yOVqP8KR|F?9jNHF79GzijSDq@+0}K9B(X_s zcGRgp$E~v1>pTJX-fN?y*UAOdp$Z40-}#;6oLgZEd=R{Y>B1Y@0@4wx# z_xF)j6X;)oa8gJ_;4@ek4-mx%#j!wfBw1WgoYr22hch>yW1bvi4Tn~0f#_q$9a~J+ zk?E_^M|s6nL2){Tc@0m>IjCBZ8GE)>lf0J_7{;^2UWhL56BLiK}zb_1p4#i2t_k5|24dvM2Db%IJu% zCMrDEltys4C~tq@Rm?!PzXv>dtx)npkCbT^iS?qA9+HjLzgJgGb~ku(Dg?E)bLLni zM_6?KTJs7#MR@NawbZUT>6x1 z?u@#)c#i{E$7ow4pnXUYG6eGp3#Lo&WgwF5tL);RYQI`M};wt|Vo|fkF3ltKI{utt~CN z88yham!g3-quwwx4HP*Qj{|^ir03unSM;gp=^=|BKv{vo!2po96`0DQe z`7hI2wEX`)Cz|E)xpc17=iE?{K#E7>u@udkoi1C}w`asG@<(`Q*icT@rE*B{vJ8W{ zIk6#lUfOkk1J@$*M}aj!f3~TobLj6_=fs+zcsNssPxJ}H(its9-5HQ?u$hxeVT@Tm zZt!CK61Jl3QHc69q}%J7dT{?BjSDTf$o&&*K?cJU#m5r5#$~7CAcKWus&U4nZ2_}j zTyDit$|fzcnY!x((Fb@25Qtd2v7$1*xct=R>ZOMksYviAglA^#{ApM0AEnTplDy|s z>G>oQA}_>lt=J2hxRFtffpIWW)WPGoxDpF)jemuIdp5HQNyN&Lo+GNy_kG{#9F@e) znN#0~@&pIIX0`RkI!RnmN9I~ceB4?T;ms=-0!tnF{@EK5D;C;hLjlmG9jGQwW7fB_ zsGaJ}M4wozTz=zp{WcQoUpu~vQ)KqwIzT;=$-Jj_J!2SjY=bl4g2^$^GOzcwk3!IP zLzN|$FF)?1F?-nkxHtD*Au8Z~g{XaXbB2eq!w$jXJcV- zU!J6{;U}yTEw#5G#%1MH~vic zB)^FpH9l(jI^Lk)zVcDCC%gm0%zyGPI+MN-Lwdg|EspNjM zU{zXYuKK=GIiG|#Oi&}s| zi8uPxFA=4bC5hz#skYdf?S+39|L*?Jf2ukoAvJ1GT8+=l!DaIv%hfXuXC17$;|jsT zxaVIisaqRPbjdmSV5@Mgm{t(}0YMzrg7tGPPB(~YU5`77-6tQxqYjxK=)ju z{MKmN7g8uN61KDt{|Vr|A&VvVA|Rhk#PL84GKG}^+A~GXrW0NZe)fS1aupp^fIY#V zkGjE3wV=bMAqX}fRTuw^wQymsl!0)iF{kT|dIp||5Rplpzf}`Dg+D1KR zRJ%up-MFQD41%E_t%=8Kxg*i>H3wc2;kTk|t#-hLlFS_BISD!WQV*}0Stl)(c_ocR z{ES6Bp^#pF5lW59%XGNo@4RSvCvI4cKWK*S?_v_OSIm6VER`Oy9;e2nA7>!f)n>cr zLE9J@Cn8aXk`k9aWY$5-)vtHq4c=A)i`4TH)gWpuoUyoi!U)0=ZXFKUHoE~pSI(v1 z&>+T?AKl&cT;=D%Mraol3i71PMZHB# zdiKhmUoMvo`x1h2?DyH&IlTMPc=YoRQ|&q?!=~`MY6qt&FM+d+2D(fF&52nmTH(Ww zO+HU5<>l=OhKbFP$u*Fw-W3xUaU6`H&}>`k)mZS#cXgIa`Y5$fJ`rH(I&}xjv{mW} zo2cvbHD<}x6}mjNxJJB&7>{AEe9_@4H|xe#1*((acZ#%I6(nDao!GdRsA`O1sY(SL zXIaFpUlOU$$-QBEz7qe!DOt%1enpUpEoS(w;47b~!yJ!?BD_S^hC$^W#-mS43Pu+7 zWNp|7)!O&E@9riMnP!eRkYHq=rLNw=cLAGDu^05~23o#R zoPUE-bTM~PPU}?nxUy9&`g53-Sy7F^_x%h1{bzSY^y#1I>{Sdw)!d4whd-EPN}ZD8 zo8@O_Qkr2d`QlKh?H6z54S{*~o^Y|_%S?)Wx@c?Z%*?$=9kRr#snW`|V;E_C&kq~5 z_=(E3|5G@>CyICOoZ&x%2X`> zFo$DWS%+_4~PdO zL?fUJ^oq9eP9u0nLLhDj>XK^?cOJ~l$J|A!RLY8s*s<3C&pC!)%sx9uz)#s7<_caC zR{?T;3T)I%_!BG&ewQ%DpWtkO*(OEq9pj&2VMwhVJDEJ&#qC9;5!g}xFyF=Ig{#Nf z?e2x^yr@kXEq^)AN>~|=1?`vD1vhU_CDhyvtKXcJHR0-L!kHpV)8RBv^ar09J9k#O zF&hf45B~MeM7!d?TM7gDv`br`XY@_|o%w_HxPcQJosd2}wbN_;!rq)u#+^JWWOX<8 zzDKxAc+V9wL|<$~Ph<>O0nVkex%`tf@ysHNj77AY7jgYrdyMN0+*~{ia%%1}+&dUw z<-R-7B?nm^}+Vo$|74&zZWma!2I&ve+>CEapvP#)^l<$|9Qfu>vXXv7SE9= z`}EDV-j&-o_*cdO3%|d-q$b(79D{U|R%D+kf24xu>`b=Sz-c8;#M_j6M-?jXR)e3+ z*tUu!=qf*6&RA^jptA@*!z#l^;WjdlX>&N1AyRI!-k8f|9a!uXFgp760-z{T+PR@wohnnJAzh z(}2Nl=k8hOBcX)Ddo!<`I5AscFOTZ&9bgbm8QqHcm0EMLRi=gz6; zteudJ8C?_zDYT&OiIY2a9A**UT_U4ym`%1uXf;WjOUy9F`C{v>5bFym+_AUda%)F{ z{H}AQ-Bf`qcH)|yT$@^~IPJK(W0O+%rrS70otuiIuY;EyRcA&Yqsrmn7BKI@wEK;5 zJv28c0?QXYU%Dfr4-KV5L^Drw0|xg3ypk%F!VcQ$xHg;Z^uEdU*B*44P+_3oj&!Ji z9jd?fmqOg4LFyLgNN1v+&@6KugIycdaz@KEhaNnpG zUbCu+^M%qqT7v>N!)Eq2aMN-favgn=v(z4<8#tf+kMF6TW`uR6QCcK65L%B!cK10a z2Ji1nHK@ry+9xKmdrl7`Tg(Lp@eZxO@;&!-4|-5w?p>e5Hs5H4q}XQ?dr2jN+j5=a z4k#LCr*T{O{v#v*?VZNN^TQYmnk^9nlvmunncggc;*CgGjFH!Nb$0K!`Qh6DEIy~b zPC14@VdeCf$k*#_v+hg#mD3y%Xi4zpl(6*=q8$D zVAv)S&s>51)|X=^B+B!$X(tww@#DFo0wm$#HSl!*ZL|IN}*6WB%?wzY> z-7#c-oPIOW;vg12BQ}3fm_NkZ+6_y&Jv$3<4->oulKF=!1hEX1Xb?9# zvFc+h{Sd85U}kQ}`CP0Q5V|a;3F)o+=C=d!hkZ0OTgP2FPCX%%-)seC6UxTb+(9ay z2ilE)225Oad^yV7K^?CRb*1(PvVF~~!?>?ROwa1njJ^gw@ZLY}@egOOi$dy>1(eBizC=)CA!*OqnPjKKD zS=?SA!Dj3W4C~>mJ@yrE^XAed_Q2l{4W?L~j`w&MI=iI#$hQ5QQ=%LnG(ddJt6$Xf z242VwGuO-#aXAI?l@os1HybUP5owIeXbjZ-(Z-*9d7*FmH<9L|!EI`HZ1e2%r|+(7 z7V7RxZN(rXAN%eRIPx2Va<9G48DKtgHK?&|UJb~+-+Ss}|GhfetT-ZU#3h}N2Ifk- z^ZOjixw-QW=WKk2un9wJGBC(bzAO&`y$BU~hkdfk-6`aK_PE43un&|!C#sOeVn{=( zoE-A!vm5hjX$dhl^$l?oo3a6qmv;JbRiB_&cyRg%b=oI^OmY1qA`6mnCkB0HoK}~+ zJfe6hWs%RWCIN+_y?RhTkHz;EE$|R|sG7^^j^m_nRZD03AM!;qg-IkN>xZ>`KNE2- zl{W9Vm)9xmSeTCU%9#l#s`Xq^B|dPd$RX`>)jqo8(l}`Fe2s!pxxUxLVMbr z$b>%unKO9^PR|^bX|#n3kL*}8X1!o05YJB}=k&if$LkyT)@Nyw(pHIQ&d!rY{&)Sc z7OADDE|gfWpjl zi)5uZ-cbfa76$Ff3+0D1gmp;@b_O%l69o|c5}lREse1CK5Qr0faweuS;_0ZK6Ghcv zPtGJ$&C7Y5KNWFfSR> z;99z!(y>PUxS$(R zgqzGOFq!thD3z741|^G2b>H;d%@m9^)-48g2%9WOj8;9-DdJtnd;^5YkIg@WhBt_Z z5YU(nd!zCn$(U0(QfkKRsMXxsbL;+1U76cIrc@@hDVjCt60ut=Zd#t!O_TDaHe60F z|KJ!aaQ`*BPiL)OYtbLC;BGq3b{DY;CCDdGTbwC8Ph@ExS@rRIyM@jTykv7IeC8}s zDJ|tnhGS_gqfoF5e{pmBN&gmyCO^E7AY1@3>0%br|1){D8<9#hd>%D&ic63^13Ng@ zufI!dKY_{cHV@*(a6x1#y!2WF?8}oW-PD6w%tuM`wOBaXK39QNB`^dJ4LI+4T5l6& z>T|vfVTH-(wofd;ORovV!{duXV z1PF@7jfm{-j@>x?RAhpp4ExjUunRq!sZ^R= z2mry{I}W5Kj1y;&vYx=G|Ng)I57T3Q7~CjwK-15%8hN9J|Ip_ zLOUZF>>e3emGFt@9jqsC{HA%73#A3f9|8qikN9khU}lrw#@N8_gLMG2~nZ4hM zd1dr|nC%gyj5VrGwr5`R$$aD-=-B%yoQUW%OUki@8AN^#M~+SgIUCy(0X!Acag-4) z$SWJ^%qfWAwNy}cwY>X}*zGDsw49G^+d=LNzuYuMHfv)EWeXWh@g-_S8i*&Q@emO4 z^Ox5$9jOoho@>@fINNl93UpJZsOT~tdJR0f8}0p@XgcZ#fe}+$>g!c5JOw3-0@@+( z(tfcb#x<_ge-{+t94y{v91mUST}PEhfv}fj8s8j4fSEuUfkHD?=$Xj(pa1?}{Ez?B z|N5W)!+#N3f5mSgf4eZz{YLZ;DgX`}W+nrZ)#GD1#}f-sX(9e24~C?@wE*-CkTV25 zHc}x{iA>~~i5X`vQ5y1A)D{|N6iDcmL(T z{Acrj^!#L?@H?n@evFNLjK9K|oqxAEPQ%&|Ynfhq5V$g0VWr#*rf#OCN= z<>IOJzlmENTI-f`d!?u2@Kenm8e#-=%sm^Kq98!t9YkoFY#GExTJMhC>pMyEK*aF-1-Q_N+3yg*pV8~iOnm9YoS?+ueR<2 zm>hYRLtX}|%-TQFAOWYy#=O&>p@?jfH#wQ9D8dZJ9jlruf8}Ib1ZH85umV+qO~qkY z!x=Jl(zjk{R%y$9-(T>%{u!0ro;oRc5lkTB00JKXP_9~`=QOanH9h3LYiVpLfW%_; zvGc5{&A_Lu2_P#z{v=Jcaz|sZTt{;a&$s_w)3QvNk3>ZfwPq9?T+WHiyWA|s#qbTN zy?M1lpDG`-Z4Wk0E)nrYJh4qD%S#SV_tLp~1$tN%Q6p%ndHwwVuhE&Kc-1*fjS_ps zSP(_gimB#qFOJt^q>C3}{nGUt}* z!^88b6;n(o!~}CyC(GwT7O=pb(Vux{`cM^)ENd7Is>~u+lPCNF?V_MVrJT;4%REct zar_Befkr%vu6|BhFPAQ&6B{pbjmi-{4`NX0;Y7;<`WKtnTX?%mBe^r9`*0tmFs{~qr~66&mH$t z7S1djy!f=`Fzp~%E&n#PWHT;5ORWnl;y8$Yc zEP|B(nE;V}bHOTRpnJGJuH;gn&Hw-)07*naR8@z`Pl@C~(9M@3yI?U9IgH7OwS!Jdwl5w9f?k#MB+Hf*ef*Xm>Kd)89&qsBJh)Vul(&6uVXKXW$ z>ky1wH_IFEASUY{fB)-2R%G}4Mn9-d3?tKjd2(v?#}6bX;+!siXbGpn78+`9N9-he z$TBM{ncsfQ00Lu>a(dqBll8Fb>eEO}qbnvlffHZ{^kluKPj^*ypQ>l3^CZsnym+3O zKEM6`#^B-HlThU8!5j#3(Y~SbX1xYUN-f;rM|R`IkpyAw`ehl6LIrn%T6QD1~*Z_`Or74$B0vaGIWY?7z5%DE_?r+#R{v+I`dYQ*kil zR_z||bwT}fS_=2N%JdUaGuqBawsQM;>8?i3ChCd7JofCF#|{D*l7KNpd2t8i6Vnq% z`7@c4w~f=3#!^Mh;9zUPjb1|uSw~g4_z`jGDT}-!8A15gH6u7(ma+32cLi$UW!t#s;j*g8o|hGnMUTzjQY*VyeyB?B(vvV5=~#r}fS@t2M(-7Y^9QLU zPaI73A5;;TCfzFs`zWBKw6p+sEFVw^`~)hxr&xQL+q2?}n|kW-!^+@iyY>@Z<^*0m zpvU39V!c8H&ZG#9W6CE9Pg6D1AUAH}!-X!r{IwiVVzIa~C~?AP9Ilr87^_q;@Z5&q z9tBu8yVHx^dS&6!;IKEjXFl$y?DCW~Fct!SL$Zy5}iG z#k52jSlV2>;Nm8!FcUA+wNv#d<_4-w1ddiRF_$V&!qJmgk)$mMcULa19(mO`++ER{ zYTS*$(Cybwo9OGw+RIT9@yiu!bt@}fN|Olnxe2U0?8dl7NLVXYfimf~HQj(w2zR(` zyyc`Cs?HC!hbY`el-5FF;d)BggfEH(C(bT=KW-hqQ&ra2BoaAVZUQZUc(-7fuyk-{ z`n+25^z?hg*yR2^k*qZss#C2#mk>tmWw=RvVLD#fkBX{pr+B`JJ1Dl!iYQDmqNN@O8 z&W5dNO5s2Jr<~LmH=n?(EA?&di-Iv+iy6d~&Ms&Kvh`~6v~%xBn>>ja9qU#4!!iB{ zw!w0L$ARPhfe3X@mtLeTKf(a;F!UsP;`iXRfWQQ9KNZT2wRE!f#4sr|Mwn>NwPPj}ozelv zNa_jitB1L5?#J6RpVZ=UB5r1ULW>0))%7B}Hqm(9V{~4;wS=t)jm8=aSNP zw0x~s9jT%6#Zb3{ldWh_)b zFqOwZzKzasE<6t%C&8>&5OJXewpnFFeP-~&%TIVOL9OSRAN<>2dCr-LXQtlih$oZ2 z0Txw5agD~3dqh$)6U7ec32hsh;?ycvv!}a{_y_<4-7@QRiwHVV^~AJRUsYTS3sqq| zvNls-$Z1F5?e3n;986cf#5^?KfBfQ9BHJiCgFKqok87`;O4KJZlHQef(34H#f zbUC;jowb8dkGC<3x`We58(Rz(2{VF=IHtI7^y%~hl6`|*!;&8FatQD^CuXdRIX4sX z5~P8pxfo;;w+Uaek-d#Pi$*2_|Cy@$ZD_G;Kvf0oI9S#T**`Nk+Vql~Qd&6uq#Ub! zQkb#gaF=vdTGC1(w?5uK^ z%G4ZV@W#j59G_Q)-1O(4^4>-xv%H=quB<;zc=3QG)L>#f1R48dFG9i~1E1HMdGqP4 ztsAUB#u-_N#juTt!ah#!looL{ad~Xoz0Q)jY{DmUB3_0hjWd$vEo}!B5x~0wX4MIA zh{Y>^-QLk=vTh~dDBUnen8thV;I7hg4a_HcQjwuFRO)=I#q6hRr(@ zXw2)1Jye)Gf!CjKZVr8XqL!8B;J}r+$DIm^xQpU8$!v+RRJZc1-WIN1`TW?XE`cQe zAhd6=lcwIA$>I=ZZu+`!9MPFen#hEfnpCvEasCmKduC+ClTg9w%CJ6JyWLWQ!tBL! zZt^z9dhh(C0E`k_zrpVu=?pH!o*PepreO1V1Oo@f+v`l!b4Y=(^?#@TDDlD#&S^)` z$A?f&2=l=P<1MK2R$1$(n+Qs@Foc7If$+N}g1tpyT>Mx&H^+JMVGbhHgDgpo>JK?| zAQZ(CE;I}1&CH@2I|n#@COQgdkSJEMDE!ljR zkl>eokyO~hEsdc9-jfFUQ)=uEwm;_-!ulnjfUutI-!)W=PB94ly0V}rmkC%IGw=T0w0G=hfIml#l?Gp6vdv_`UZTLC*x)rMHo}M|Q+R1N( zS3|O~FTY`$D4NZEiTxyT3mwTQWQI!*?-g~k$|ws|;4+kBZlzFu4DOoKJ5G&^WkXYE z?P_VC?e2UKQ*~RHwUjqvda|R>obw@;*s`*CeGclVs(H7P#{C zW>@~nIgf=aD$3==_kXAXraK*2y!N*GeE3OEwy8-=6+|=7LxmI{;=j&6|GlQEUP0D_ zV!+eydA&;878040q(0NPm}$w^@A*Ba-EEGx8R0BpW@A{p_5f2rtiQhRjF>V>&ORM^ z{mUt571@UwGo=bo7P2QF{)e9`=LL|QnA!Q;@tYHWD<%y@lhh5H&prxsWl+gCtIzb3 zwmo<<{*i_s)AscQ&eCPdh+tJ7dH)dd*HdS-)^@5wmg(a8@seX5#43Juq*37`u58DL ziW~wo%x8=Hjnv*o{H*;LmxGXB+)iBlQaD=g&Zk;tQKqCQk9Vfe1-YG}x*>3lMdT!n zf}ZdPplif#!3MN=0pfj6e8>)ScN(JChNsW-q3g-Je*0l8kj6bhr7AX#NxDtC+PJ>C zz8kO1M3%OG)H-3huxvNb-O)DysZ!tlD6m4`PEv}1xB-$mD%V;YG9@XP<3{P0UIJ zl?{MM>LhVX7s3DheCF!3TSkD1yMowlFg_eX{=f_hcb=z?P2jTxCKvw(DF+5M`)_n> z7t5)#kZL}ODPsV#QdkKvm~9}JC$}_5eqFM{%HDAkE@LxSiN0#*&A=+84ISwVy30?V z@WoY*Vyyn4r1JpBN?ENZf;?Q|n_V2N>CGBT%AQc4%B0{2$pazf^tK$3-~z66EoFu^J!x9p?^N_pup# zeCBf_yI_zZ6O6tKrCVhgb2c-RmFOJYh!!*u8U1>-$XOcN&zZ_SkoS$8h@#0; zBY8A6zoLgg2ww(0vK?1x?HYDqELlQyuel(ZMzdKiOiVtJ#df9U{KnJq@bxP|Bvjp? z(+Iy^7Yg69qsQd~nU=|?_H)Xwu;xz6Q?0`LS|A#c6;1P@mCP?0;*Xt0>4PJ;ywg?9 zB4;DW4^u-j3h{7Z=uKn#YFz0T=2l&XJp1dft0Erx8U7g3xyiI1#T{UVP8-d5Du8(@74~X!+9omY)y7@)O$Ut+l$BTx3$e!4&N$pTzwAw4 z-)VKq&6jJ~`4AV#Y`at#Ik1+SwX})l0KdJnYYmUyc67Fb38Qq@*)CsODpQ^Yi3=2+ zT?Y;G9+8WN$>{U^60`OUY*EG5F2Fdc4!Ycfs4|$JW zo;me#a;Ab%XQ^yDi#N4$#PY!t^`UHMMLs%`ix~FR;)L-&u5lPEL{Py($P_uof{qo53t>G?M;@{W zhE5UF4?QkG{?t>Eqm@qs83(Nxj-HwKobEGy7=7925QR1|JulGp;5Y04IVT>}bcx9V z;3ybnHqLaUJD9vmIkl#}%9Qm&enKW4FhEOmIzLB`@zVRD0Vq}XmWf{(CrkFz|86bE zBW}ESoU4CsGfqFlmre1SKO3P9kkh`%KYVVs#EQfC%cK;|By!z-&Sry~*RtDG=1H(j z=?l=enBxag>WPD#n{s!*Ihz2>GDQ_Ymvtu>DBsU?=H%+I#L`y; zrpmPp*5#L&9d2dg`m&W{HyuaB7>^a^YOqC5h#N3_3pamBJyYfY;S;13Ugg!Ft%#xD zoH0es2Z1}EK6^X|w z^b59~iF2^Y>Omu0OO=rR`#6A>X>-Zhx3vQT`Kaq_A2OWM^YY!C6{Wu8#sTPMUz+og zer?>jRZ^{^FJkj@RvN%Fq*W(-hcP$uV+K1&9Z#R-gg;*ATiQnI7`OLZ;T1m7fMpBx zj&T)jtXlNZUcz=O8x21BgAOCSuuf~z9fB49%KF54DTRO*l|6oXeIIJ0tCXP0cn61~ z&?8mt8S~FWKDdPvX2ws2%l|q3P4WDg#?3cw&%skXo)7K0@v(6M1)DKF*OZ3e zPJXv70Ks7aRnTmt;iSyID?u){N>yt$q4)PS zytp3{hyiGV*Bk)jqyFir2+ z6+fNxo<2w5Y%8Sx_Sa9&i9Slc-grHQG6GNDop{)H#5esZ^#(r8A|_f>Kr(f-4f`tM z_z5x-ypKxu^UCj}MEP1XNuaErJ@=3*AE{4#SiYS2G*;X)Z`2dqI|!Vuk=c+We)D9*9_oM1*BRqdF1GRvxfSv~{tTZohB`3?M^ z|NNexXCnB6stOr%_(ynit7N^mQNnFsAjuo?@_Sh4m2_STP})IZ47%MO`W?xSI8(ZJ zXQhx=&!0aL#Q{7-P2zw$`Ag>oA8tbnN8h!3kKLwjb}=OF9yCD!b2?t8l;(v15F3 zGc^U#aqYsWxp1_2X}H-V=q@%C&WzvQ4><4kBPRNV)?nvjT~41BM9vmxsk$ai1-Iq$ zC;G&DHs3}X2x=o+J`OC{`Hn3x(cQZwoEw$2j+j33=q}EH*BtZVxkPf@ou;}4?M#dT za`xs;6Kur1Ovmjj@Xic8(-B{rC>=&EQO%*r@VQ7{n?DLC`Uz<$SU)7isI%6 zZ!9XeI%yBn9Gu~!@!=1z;?_dP*V%!S_osTqE%5I$a@LIVz2P%cm^VZ;4s9mmERU4R zqyG%o_!^O5B-@;@sLUCP8zY;P6k4McinSwGU$(MirlXZ;PtDI0FQZe}y?=*bhJNG5 z3~xvh-^V%g_O%B*i@JQQDahM$Yj8!8S8E!QcR%lT%V0p+483|@7pNTmSO0ud%F=m+ znr^2P-$&;pH@az>ATX4lRGIR^5x^5;8R+VdC?RAv3MWtxOllc>p_jQ}A^@~TM&c%4 zUjNhx14D^BmoaKBIo{w1-w8)?dZ95Tc1N6`(CaCQ6P(D+V4bBX~Ob|R(K+X6G zt<2|V;Iyo?v8M#U9EDBgti_k+92qKB-nk*axUtT-UIkQ2uAH1>@0Cs~5f9+;1Z#OC)f zBWD3T`G_DN&`4$;t-@CBa0F;0vK6i(oHJ?n3dap;;E=}rd9#rycINL@#nIl?no`Ci zTh0Pl1IVbK&-HfG;dL~pRgl`8{Lb#i(~@s(`@JV$?P4g(%ef<-6goc;%5vnGfC7H5 z)(l_QP7*<1CI2#?y{f33uh=^uW_tiwc!c$q23yUW_ZA?@=He&;I9w`FWje@cRL{pA z6`g1kmSiU47=|akK4jNb67$>eUzz5R`fw3BnV7uG1RZ&)LsNbm z^E~?1rG8v2;Q|;YrmBRncf_BcM40LfliMbXhI|uExx}oa<6AUKe}otl_)#DWft94a{x z`8+@-Dw)GP0VML7flR`>em|S^KFqa1=0yI>i-{)&we(B2L%gz;%Uvpa76lo8Pxl6S z{9nzwS7JX3tm!&TKVD98$MmnnsAp8?0PcAhU8oa?4~SI{K8_fTmWUE zaPwdk9M;m;=JHN+WVc^3stu@VbDjbtLhm>+ejJml{4JJ8b56`md1*V=t(8M%joNeK zyiaCD-KO*#tDV-JSodp{yJu8_T63#LR17T}k26tHQK*YPpZ1%a0JScyKjUr%NkTt& zFQ&4FGAI}@Lt}H#={STfVahBLPIAv7g7`9rVAOvP*b(qc%!qjKQpoTMs>cviEe9H> z^*zm>q3|oN-iIno18GxO`N-9-Wb+38Wqh>vWfWF|-DX>H@Iw$Lw6>a0Vyi+;N$T&& zoad3yX`HDWBVIwD!1<^;9Zbp#cm_Su$#E?f+`!+qD&AlBoKuRmh@D~Q#{D|Cys z)4Rl***e1R*pwxE<4HvWX%U`x5fVq+7Y@oeUl?~~S&ql=p$B{WlStlg+!4*>+J9|oJC9NoQ*v6%c@}9YoWK4m6;=H z9W=UWn(Ghzt1}`h6Dr($n6ujGNs5#x3~FZ0MH~&iaw1VrkJaoXclCH+_8{*VtX_qX z#o88sdoI7(`D>dXbGOckD7mRR) zShLE~ir$mDHRR^6@U~<^JZ1Qv7qgKK?uiq&E#mT5JniNWMj(WS%kOR22E(q;Gj9j~ zz?7H6Ag>$Ou1p=N7Fi0%^45p-oSiu<<+3vTGmQ6&o(Pltl9)c0{l(!Vg2~kf>S>R| z!8evOzTs7+B7mi>)bcPLAvbaHW9oLe!+x)^R;Uv;Kw8Ar8Varu#ZC;nb{*=VyxH|X zv`#K*t+6&0PnZveFeHCs8^B|8+C^^6F`sj_5B{onq9!8C7Rnc_c*5zRGW9hc(@@xW z^OVVw+{gAIkrKNt=twVEwB$EF;$t!Zf>kPdgyf-wa$2PQ1P$7o%l;?D8^LWY(iE} z9p}AeaX7_s1=3lIZqnGB^QIl`!}x zntAm#Xy}h}h6LLc_)Ke$(%R*6Sb#SU9!v9D;+R?J)@9xM{W-SB;0Mg4xh>r{409!a z$0zyQngmIlqCOd#@1(JaX&G)~ef4`|2shXeOV%C=b;CMO@H{uFM@xQc2Vk|{`Jiwt zfhK*Zm25O&?Bk6EH?nb0T$tp?{lNA)*B_2Jqw2*rf>1||zWLVc{sd3N-4b)oFlmjFzAXPMlEs7c%TbosiMTr-(tTz{Q@6Pi zXv02jyo_}^ECFN;RaUrmxPyXIQ}_fO{rBgM(>fM%jo({3|FIyJ7TR#~$c$nrw5Q~2 zQI_e+efvJ-ylXhG*IJSWo$cTHe%f%tjW>t5e~u%U*x81|CKs^6ld#{*3G?tF^XJ z6EVrukU>>e0>iwhj%8_0AS+QErcTm&JU#i~`FjPr2dhL`u5rfllaIGc+W8~_FX3l3G?{?ZVstFS9Y*O#OwMe?xy-CP76?3=~ zNDH&^IZth9xkg_uuDRUX_hPS6hOEAhbjqQmR&Tznn;aZ8iWh*-UwwBTFV#cSoxsJp zMxC><%sy5>wYW)FADU_0I!qwhTvg0yBtg&WhCp7e5rBJu>WVNN5RhiL$Oh)Ak{Q@MPlj34^VNaLJL| z0tMofdK!qToSjB-)?A3_r#oW)^?f@37X8lbl0@aWgPrjQSZqG`;zDIW^Y-pT5(-(} zDb>CjD!i0b&zh#!ABb<%^GE1DPTa|rcZB=r0OI-iV3QYI?RoYcuh@D7L|*d? zJA73AapR2RXWJFt{eeAVYnPknn|@T0wmPr+4$yRXw-98*=>Osq8*ZLIx2bAu=EINh zOUr+7Gc+Ym$v=7@2&Q87I87^`yq3*ycFjr$N<2m_ibm!oKk_y$6yq#)Eb|f^C^+;L ze-rY=u=Uu{&FAC}pg+`Ze@OsGm}U_5SKnK0d8(s zz#1#|(s5|yx=DRG?p`EsyDOYk`af^T;wLDf@+JvOYdY7RzW8N0nsM>e>h(;W#&UGM z7mY_#3CRt_Se}_rErI)!%(+p2@GSHu8_h-a20qJwZ<0FxSWEN=fhp8T+&%4#l}tTL zKZ!+V;*artn;424Jg&#AJNh8ED$Z-NTU~U1-u-)$e~#-7p}2}>nq2N+%~KDXt2Hhu zLXWpK(iy1eN7|bGFIav4dWrp;enzOUJZG%|Zb8K!lrvOf7x8yDv4u9s(P6mY!^~N- zZ1;U0`ul}iIurhh%tx42YsOLF&Ndfc;&h~@o>N4kd%78`RZ%IJXLCP0 z-OXWXcMQ$|Q!w~$&Y0xVb9!1EW5Xf}Vu*$dhlrVq$-bGvHJCrHW;mXA_?! zM~BWOB<}pekg9a3F_(qC76tIq9ewJ*g~d|Z6f3vxn9o-5s{ZiF@3xt}Ziwt%7h~_^ zZPLaWLmEDDy@dKeM>clMLmDV+u`d9uKWM>-$Y;ELRtfRGF}?ZV(i}3fY(AME+C$dG za_F4QORfu+(Vs-GEf*?nciGesMc3GmI{Gcb62Vo-pg;rC+qb|mMGRY>CihNh)--Th zgS5isi}DjG4?_WYB#*R1c7p9!w>Or@#!9j159O6o=h|7RH>98GOngRYW7Po6&Jhr@ zZi+pX7U^qr9REBPkjMRAy%Z(8q*hL2H4g95 zC#H75R^kEO3s0$OK6BHM?X7iO(f;vQCWaTw^G(|RgTLS3z^9RNdaFa;{_E&{5TqPs~pMc#!k{)8C&L5Bk9jx__S35li#&97If? z8Y1^|%vI0Ks_a0Yju<>C%1AOLNr`l9m+G&ly-LmGY$)E}H~MUBSaAkCO%F^~R8?j@ zKunx-(&8>12tpzxyXTpTGaV=jS*6iPO&A<s?fG-UQ;zY>al8qY)cc zIpVdXmlB!Ar*S|2h+oQwIn2n}f!a5BkOvk8LrZ+w+-dPFD&moYPFuHBc}>KsF12L4O-3r1hqN_A$EPft&4v05+U|VOPti(Ow$<15 z{l&tMH^#9rUeCd==WC^5^=Za7h`Tbfyv@UAS$RL?DNNKFJAzK43xl|ZyI3V$e2~Jm z3;$JXruRCYXxyD4tK0_+MwTAF`5-w8#I5xcH;QaqwfL}$34fKvdaFgoLn^&nYm9r~ z11S5tuH4sY9(0#*R~e*R6IdC2CK~jWRK;2nl$K-h1OFoj6U-=u1oO9tcTWM~#BX4m z=W^Cgw`~^;g+50O=>-Yxo@QIwxY-=eRq@?Tff=Y5=K1WEn{&|lJ)PgUj9|w`F8T`_ zJ>w%H$Fl8q5X)z);T2~6_y5h`#ky#rOH4mkwlb(0YoNRiKv32!4zR2NlT{h@9!WCN z?+(kFDs54yj5`wQOWu%3J*TMrkTz!|6l3H>ruNcEd1E*v$V|li{JrYwO~+BsFb6?l z$;1ruCyIy#$brf4t)9U+0~Vt3lRV@g2Cz?GKrWN+cgDA0SmSE_u_posi9YJN+65U@ z*4$MfYn1C&mE%{wj;uc%xyu>eZ$q+7A{=pn`zwHv+|qS9JL8m@mvDCVa(uT>dk3yj znI@uVCXUs%H)=->+Gz^^uNeDk{~U6ik4@La`Ra_?KH1>Pp7&VKLp`TdV zPt-?m1HzZ(8JeXPs2l(D?|=S3{O|waZ-0CKdjGX&4l3uJo)pGuhI0MZM2 zf(9<+22EXv2G~|f+#5H4nqEF#SE>+|ntK*X_pmm?it7Z76w83c>tseIy5kIYZoK?_ zE%oOjX11`XZ(l<%|4#JW%obj5qw&Bbb^$rawW4 zaPTd*v4fBLFx8qHT~{mqTvox0E7fj=s7rhYM|o=A`tP9SbF+D-=d_V4Yfw$b4PcvU zBbN;9nOlg!KU#SXW~c;f)C7>>|Kh}-| zm^D}ivIsOr5@gfz9;Ydodyv=3M34AHWzpDNPY*@hja zln&+VZSEj}I9Rx;l`Nu3EyB7d5^b%!Jc!ycxauCo={38pio0AM6HFRnzOLPUvyBmH zr`yCQB=4hA-W@3eZ*NI#iXl^0Gp_$`xBZ;KjmHF6F|k{mJ=iDuL_L>TLK^=q)&_T2 zFe^lM*)V(nf}M--ke*Kt8|D_cUYa#rGX;-Cr5tr+=0;fakyx&>IKKS+D>`PY6#tkC zwk-M2?)X6Sk-T@=+)om7sWF7x#-d?9U!6Fp3G^8?DIRY33gh)ZBe&C?3=#S{ssK z;$Vez&`8?ODdfxO?(Pm-_dCdXB12snk^pIp(S4%d_je}d44dNV7j?4M{Y+}vZ_KZp zW#p-V%^7lEKn0@jk@Xa$Z)O_K{%YV9(yf1juQyyL=iv& zRN1msS8(-hBo-jdyg^q>eqr*D9vGHfN?v#9V!ugQ6L(hqy-Q-FkXm`TNQUeU^8;@U zgqtVLG5=3sqlOL4UJKJO#gVVm!Q4{`lJC}X=LzECol(&(8vELz+ZwZDvj1*A%b;sY zD0V%*sTB`mN{Jv}#;Jy{XSUSNoH$5H*5{A%$fw(TVp)Z(?L?r##|I)7nt7fG1fGd| zJ$QqS6FH9g^{`{JMK*-f2{9zR+H4E~v15hM&?q1yvL=d=r-O%q;6qE~Q_)VlYo*yJQ%5pjsW#^3!r|OK{5WJ~Gpa9p$=kM|BjAl;IqMZl zA6#l#FQDb74mj;F_J`&*|0FJ75DsQ}aOUqngP5#A_GBUE$*RhzI*yY){mz`zXL^8W zdW0QAf<#aJ+xgv(rb;5hL%r)jDni7=VSba=nTb?hCgJZ%YYLNIm64( zT$fFLEC4$oQ$lY}n3-I1%bQusKeiN?+Z|Ac;N@7_vSc@(sGpd!a0XNd<8HLVLl()= z?-dhXR}vP+E370Mz6$dRlv5B>lqFiA<^1 zH%hvBvYpxRDhE4H$4&pr2R2uKAT{T%ydrk%$bbF5m!rAlxBZKoCkKOuqv*In0EdDY zphDY=9o)1R49@6u#?r?5d=#*r$;JDfA&wzP5$jiKGd{Qx(V9wI9PiaJtmX8P7g7sk zd@bnpXZs|{7*s~m)SZ@`ob!kIKRayOH2L2;0@N_URD|K@#+wTcf1EB*A7TY#9cI=7 zz#&{-MTz=9#?erHEuuVya^aWeHEeh?o{8==p)AZCE|$;9;wy9{xJ>e^g6vt&+^C2*bsgs& zSt=z1Nd(Rz^)x3jbDix8oN29#o^=yND?2M%AxRU3ep?YE!AUnFvI;MqLX()TpC=zd z2XXoeE!!kacux2ZcW#&yD91qKDnr_<-Toy>O^-!=79*NfXY`@FD-P*x7BiE1CG>cYm$dDtB>a|!&Khp$@l3~@##1*GKCH9F*VE#qoRVNa ziGnK)#jN<2MsE@iMizOmO5PE{$FnG)C2S89Vs<7}h~Je$iD<9SO^?lyK%H}3VBJm9 zuL@K+mzzgH+ZF*HzzYmk1^D&L<2lwuJ&^R`4`ql~+2&i~Gxypk#hK~DcR_RXRrPZ` zoE=ef8f_I;SvT&n!^}W|3dxDUoJQ4TfRBGWw}UH))8T(3OWb%TN~>(d2|VEt1forW z3#5nz%ZKZ&+vFdYP~MVN4(&bC*Uy~6?*9#Ia?ua8Q!BpiN)V|hDTFG91M{OAyNOMU zy=uj%O97<_C4>~v%H@#@B^*tuKrBmlyr+_xT$)@iwflBI{AV+-(TH-UESads*`zW@ zP(c0QL}VNN!%0DX#>pBXiULCM{}r%Znz6%Ehd&qa_3Zti{`Kc?)G=ocUL*sro~olr zYu*e_JnWd5$^;-rc28E4_&ho%@bZ3*s7gLuwt^rt%?Uvd&;Q9Psa1M<4wBl`iRJ>= z@{SDt<#eiNPh>t3ar(sjZxQjYQ~yB5^P_zkJ|Bk?5uS03sqsjg2H+%TZczyL)*S7- zkFp5p^d%wZ$F-mN0ql_{q%%q+%w6E8-ws$}2Wv4T#t|9rli0C(7DzEwp^%SeTTqhI zu<7RlGtOjb37MLRqUQ>?U{y#cNN@U;R7aVpmp5dcj<=g980rhwg611X=beY*W4Fe- zi8rh!4VK$loKaM^s|^g?${UKdhvyYg2qrXW%*;uwr1RoQc^<9@p)o;j^DS__a-04| zMAQ?{qnRUS^3ha*sQjL&b%q~D9xR>cMm}7k_$NyTn*-LOmw8Qidtng&_XjTO9H59V2BL>zJ%!ZMoAAgmST z)sB1AIA@nv2nTO}1axWWJwC?Qm}nHeW5x-lG&MJh#_2krF3>K`lnd=n>uPPidckmU zbk4-$8h?C_*(?CUIG@rn#hlC6^7Uou)yJ(MD!2aRS<)jctzWo?C&O}hjd{q(;+&7Jp%Cw}g7<^s+wJ9w2l-f@{r32iU+Zm9Ly@ium?MLZDhTn*JyXZL5fgZ9 zvZeOe$|Wa@MtNuk6JbKcP5nknLD$RAvP)@vu==`<;LH}&4jR4>w~KxB8dgRev*rBj zv&22PyopSe1d3x5kN>mqv5Tr#s-3lZeYewLKuY6fqN;d1$DULidm3k^M;{+-Lsm7UfVLXiiMtLSgPG_K$>XJdWN4h^vjqmF?V4QX|a10r(q==~6J=+~{B7SVjc1@pb~sqr4PEX$L)JXL5VSC+*nIlgZorp@x4R zzau5E!{v*?OY>p3ej{@MWSUQ`ua%n+H6-@~IhQD45wH9YVt`SH^LU5Z_qzQmMKk}P zz9~TRwe~D7dF$wuuwl5!ON+}Xp1Tmv=lZo0*FDQhos;htc24}yMGQ{`GAdRH8OQX6 zWD#t{wAeoxaH!_;3_dRYtKNeH?#)8B=Z7CoU9||DrkJjE8h6vk8SVHUUvTSyFoCGZ z278GS39P-woiB{%ZaMV4QB?fuSyF-r70Mt#8Pk9J8?xU5rk;RlKL_|31%u`b9qs9r$Y|#$&3;Ct6vx+cnK5ezW^k4#Xx6ez3;8I5k~5 zPRoSmdRdpqE$o2bsVqIvrG#DU61nVMn)LjE7_kuI;-FaZx5WjG9lAR!&ZEsM-RE2v zfdV7k6|U96PPy=zXS>1Pk0(D^nsDNm-lI$Fy9Sf2h-W0(Fazm}RK$lf(36%JL^qBN zW_KEx*G4TXX9FM z6})*<7(aWkRB+lFGk1=rsAgbUIIBvS?}~MER=AaNopYsIH1x>hurQL4jWA*kC}&`Q zq9YmsXt>GouH$Dd3p%&->ZbTXV^(}X7+Th0Qsgf92)u8QHoA-L`D}k2Za+>;{bRih z8;`@3sr=9@w`E1F<1X<1Fn4`4!?5KrhkV-@s!dOS2Fk0~^GE^~=PiSJOiN)=ekS6* z4FGqF&!cDZ5;uTY8JcT}H7tHHF`sOVkA`oAYuyBm1vDC?AXrg`XLR*75<3CVNCEQ0eH#t`W57Uy72scskx$xKs! zyf(k-3>YhlmYSJDJdFIOmo0ZXo?IcwtySi;8L9;n@CQCPbGPCvDUH1NPpSU#QEJqJ zAwM?p(q~()z#Z;AU}CqCzbCTx=x6JbQCVCjE~W;{002!FgkYo9z#CRHaYF zj3*qGx92CRCz@m3RIDCB^wQzFEq|(HO@EL-9Z2FJH9?aw*(NgyP>9=R4cc=eAsA0hTx)soF zXo0pF%nz*D5T~4YI9Ro%Eq!qHd^i)~gC^pp^i5QKp4V2{vbtH zxdL^NRd8?5{0|(b1aUVQm%YIP=(Mun;Zb$zQ-}`qxvu7!IMi$-Qi39C;PIFpG>2!0 zRYP3y9oyU1@jIW0EXoSzQ}-*k6R~2^`d4?U>XNj0>OH0I|&{?p(8ntwgN|6rQ;^OOu#a{A4gx1&>cZ6q>IcX#w; zpekFH?~F2#0)f-7Fy#yo9&~pLUxO2<6g!fGY|ePHk}KlwbIv)Z-}gn895MMMej<_i zll4U4Fnup4GM;`X@?ZMrUr$99=AFvG13^ktTIfxQP>irKZz^zW!L5oQheh&PSK@uK zx55AbAOJ~3K~xn(?}LvuW;vGF?=xVOl-~4NR4||X-X5cQRhCKbq$fU65iOy(AXRH? zQl?s7za@V=IKw_xGhDt*m!F@O%He7zS9`Amdq_0e4s#NxP?d|8b*hr=_o}Wo2=K zKN@kO+zR2oTr9$t4}!p?9;Zaq6O}?IriYj}lfpVNYTrB+ve#h#oP^3+n)S#Z{1rb- z6AQ4N9$e%b0kHh)VcnBeW^)_P9D2x$ks%fvl2X~XU{GHjivzSc+^dyXy0igc@(uF8 z)KtX0t8*fY9DR^*dC`d-=QT2I#tHLSWWZpexW_L!If=q*!&bc3(+w-33Z5}W`%{Q6O?_LV1?ZsVaWlQZr}trUZ~QWj z&dmP-U6^JO#~&>EuMK#X6*nKg)E-dyfY5Hxo?*zAqNa^MIlJ+8g`0bs%Mp;fCQ;E! z>-g0%I)jk>g{y>>a1~ z(n=1gFJT>=?ML)Or4CuMQF+ruDibv#Fpr!C$2kSzcabl3kx3O!9rrkUPBvF^Z!=3& zgwMoK;lsxuDSb@zVBUNpIZaeEsi{>4`aMXGh;52Uh8M}|+1TipOUdn?PEsZSCMZv! zaIe6&P)S z;zq4v09vFxgN+ryEZ_1z3KPrfeCbT@q?nr;VfM7?_qKPy2``l9f{Rye>y&E8Xf@C# z5|68vR3_8^$JN{A+LjzidTt(T-%OHdsq`oT8ib(XX_%UX0HGTtsj8Wodxe`G+{_~m z3q*C3#e4Idz1NEHAG6O!(=5i?GxG>%KRw2%u2{Xbqn3yd@JOsBCu(OoW?K?6mInwg zucPx=?9qZ5ls>}3$%v}=Qqp?%pI1Chx@&bJbzteG?T>n`Be8fEXar76&sI8)2zFCc zq^TNo{DUYm*JM}fdZ-bVcEA1HB#I5d$T9;Dusoz9J^h`+L0|}N}NjCL0_T`SF2{A)E+y= zQ^;HHF7{j>Yw<|D{CibKflnyOUZ-y<7(UZw5NzH4tko)L)&{t8DIbTMwW~I@I_ps{ z$||^eiGr0}%ai>|WCW^6pn=xa(|P3TrDer_U&dw?>t$n#TRwzbcryMD^cbFYxLDX8)G>hSi3X+@M!rkBhdk&q_FR5xgVU!HFwhCQj;AN7nU&m-<)y%ren=5~?hv z!`en4P?$gtr>CJYev);tZtH}h*`Fm%{#e>gRir}fJjQ}X$^8kubt=5Kzhv9SNDs4| zUEhBdGb$o|R%`*oPAL|SEu0dzW)F;@af&C-w$+;B}t3_gbJ*@%s5&4lC}Kqe03&x06qDPN8i_pLM3J z@sx7#ytH4vyX8cDo*?6;pZPH_l*-V4v!_AZ=F5qrLN%Oeb;H=BVfMQ?XTb2Hb#0HQ z-CFK+2i7jC)QEI!t6*x(vC)&pcvh^oGBNm`tf{j#^%$tEOsYCAeNF>V$SV*YJ*Not z92vr?dN5t1RS^^6Ee@|yan=#>o}>erH*Y}q8WNrQU^t()*{H4Osti6VkYLM6-g(q< z%G&--*PwQv<2eR2-fDt(2OqXWaKg8yc-WqYIB|*&0V9kh;U89P4)(KSV>E+sbw07H$M>1)}gMjh)B^qPS2~ zVUz|xf|^zZWABt~KhXX!L~X98g&MYqR5Dwro>+@7iBT^}lO6;QJ3i1hJn^R)#f(+4 zk;EssOY?)cvPS9sn35-$_G__C$(pS5#BXSWwGkEho6G~av|$~M-81jXSQtmufvGeW zOWG$pZ{^x$j`OgHU2+|49-!2Iu^tN{pebMtRN&&E^os6j1y0k&>fj>5GZLc%43$aU zbwDvY%$W@uoS&hGGG4dRsV=hY62R`GFzqbwv#watL*Ce1G-h2qykIlB47g-`oS8I> z$oj#wa2u(0amf-QM1CyUr)j5F`$&VV4X$8tZwH#MF~-E+kr~T;zpl?Pk3>ps6c+GE z`rd{;pP~~%o@jMUA6rOM%I5Dp2DTmVG)3n<)gxF&Ty$6IQo9|bh$b<}N+(aeVqgs4 z9x3lWEC=izEKY2suk>VO_6p-_`gZAPe#D}g#`SosW8B*r)1 zZPh~5IyU(+@Ajf)7oIClb{4rUV7^B}k@BbZFR=FoSiN@g2hH6>{eJe+Xjzqi!F3>N zQZ!J~TrO3RyR-vuT0oUB!o5e!QBZGbyyvJ%o55OT?d>C;#dv!w$f^!`t3qykp6B0w z$AA0odRlV5t<+*ha?75&$t8)m;Bys~vGU1xzq)PNcIsHGM~zWE)1vf=Emdwv%DeZ< zhH)SwH(!=JD^b0%5*3T&-aCPnYjbC}F+mW|^NUf7z1!LN_55f2{{Q~*C;!G+xyh%8 zqj=gQi=6M&8?j?@vqU@*ER1bCojkd}in&jUEg8lRWpaPD0@Y03qBT9SbpME$VQvWZGT&CDW&In)M@(;cOJfm6~s4MvGOKoSULkG@GO>iiHK+ zr2NG#BTZ9D=6#sFO2$+bI_`_H-!9Qap8!fgwZHC3CF<~*kp1|`<@n@;-1yxsoTTd> zoLf-$Ez=%(!dGzYb|=ojr=Y5Pe}+LdLMit_k3*XEx_EZvzD;ntK2oIARtVKvpQ+^vYs*_5Z2b zImg0e6HXQp5go+A!>kd5UR!7fLA(nuIO2XT^mYF8e_RJ2$R4@ZN8 zQE|5hQ=5-Obj+&1h<7V(sQ>5VE}bT$o$!}A_9cGiWWmzAtA0T`Ois>b1{RZO4$g`w zQkK=NYpCm0B_M#=tRGZ=G$amy`fh?;0T9`mvVJLAS;%%d5LNBAU1YvR?&|DG8rloV zdOKR3(JbgTmvtI@ciL6V7>=9b%UXy6nQ1%MA|w-O-$xs|)8JkOKX&y1WIj)~R-fN~ z;mJ`la468a;)kJ^y3fixGM-_~34g=P%N=x9-pkE3AupxIIT&SH;gr$kV6O1tI2vZG zL6yvv(AXil=V!_FG?nfOk9Due-8^K+=k;E(IhiMPA!DBmHxVhP!4t`&C+7PgaJvY@ zE%NAbm_tNyYozPPaZCe{>w9=eHAdMy&KdpKgPMHpSPbro$MvtgaetB!Lb=yv`!XW8 zOdx0M+SVi0(Bf6_a6tzAP$tVBfk*$9?+*y=8M3B2%#pdC{jjBCaUDzxoO-vzlZI7D zr*t-)x#RT;N2sjK8N{d}yELI|>usr0RomJJgh7p#Jy^x$obiO*o>R>G)q#hHL-1yX zlBNwQagGWN5NXp0>&z=)_wrE>xwBPj;b6c8_>1ThU)S{xuaA5x&TzVq{&pDJ%7t|; zVR}PUjU~>5&oQb+>#VBNgw(7=aDHID8vC%pX%y|m(1rdiTjPCLS(X*yvVU{tCKqm} z1J~O|Rp^O4@&W5v@%O*QlaW0&B_F`a*ReADjGeI6HupBkxuW*&n?BJgq^*Ij=qaZo zwkv>Q)o#-dTLT2@FqbDMm=;wOw_wphl8>UE`X9_-wr3c26*4tGeDSw`{_zcbBwrGp zo7F43NP6_cgW2=R$v9F>eQ)Jst*{>J5pW{euvGH5-o6Xj6Ta;?Wt+5-&9=2wsh{3; z%(q}fy(bWUI79RQmcf+p19`v%cA)fO`o2^lvXmaVZ{oh~12~%SD{OHM=+a`div3D< z7-rIXT)+p74g#0vcyS+(Mwk|$s$}KbY(2EprK{EtXG#N69O@q zW7ikIjvAjQ;dR_D?%uQ)W~fmWUmJjX;F6Ub)*L?XBbqs%E{wehgMY1%GwlkO%GYsM05`Qq_-W< zx>&(e!yj)yhH5z=PiJz>mT$7w!;7xb23)9~se`t_u!HK6skOt>{o+qir|vf}snK(b zwcIS9SZF*T_iN|yZh0+ds2#jquY;n9W0~U>g5AH#gBXVGJtosgusm3gxMfi}^;O?g z?K#R3x;whggA~=nW5KnMt#%-=9upHHVnt@GCpwVsl#`C4Xuz`M22;mskqf_lo+?tg zaRrSN2a^2GKIvqM#>^IppzKs!2XW+X@= z&w+Mn<%(kVw!*$hl#-c`WcB+c>mtt7D4CBW{s8k&Bfxpbq)rF-t8YB6AsW2(9T0a# zC_JL)AVJyGH^iNm+!C#)OQ({|ZY;AC$Dc ziAqSR46L=JMk~dllv5>UmKTIF4N6nMMy(6-x9$74k)&G!5{gMv{6;Ce85@()qcW=PB# z%n+{%2c&k<7johWtx3*loJ!N}^6q*r61z|w{ajk4V+L=dRfv;d9-~~y!z&+iLQd{d zk4d4gtbzZCI%@OjpF}e4Hww+qV5z z7dXwCb3K_H_KJLKn%N7g;Zna&s}>hdGk>CMSDuWPbtQ<}xMwB5*C2?h{R50K!J*uz z^buHl-iv)EHM74h_DuFx*2m@9{NYy&Ut{GGDj{!B8X;KM9sv6W~LtaUb>Kk{2)>bERYS*EBO^8U>Y7g*PY*Kpg#8ZXA ziaT_js zA&X8I6DG;d=FROymbU0Kj-xf5UVu0hrGYq_Z-j`$G`drT#XN|k(8!DRJ)`i9%cYr7 zU}uixh3ZuH(n#tYHk(FiI-3N*J)}_jVk0VM5~Oru(Jn9|b_^SWc6OU1^RF1$f63xeY&)9~VCXs#Z2C zPd@OIM*U^WLUAZ`;=x37J>djh6QtNb?J!^au4*@5i$v1j*TlV??A4zp%Du~Bc$@(6 z*|)d1SI3km1>yPHP)oT_m~~p~o{0wdmq!|(2g?x{4O_`LBeMY+-lML6A=Am;xRp}L zJa{ceBuXdzQvLjRK^Wys&-8@u@WaL#8krn#%NFqJ>b+A#Rj*!T=W#5Os@c5_mT<^K zuLgRN5nwafD+<^vE264~w{F!Vfh{k^Wkq5FDFu=C+N* z7XsocASa=&+orwMAAEi-JaUjY8MV8YYv-*6P9f7vu-EY})+cM#6WkjT5;){=Ry_h4 zmG3!f*v$U73u}oTJu#0et%}hmC_CWmZdCKg*5`J~^~1q3ZQC68tOIzQ(;d&aqt4?p z3&|WBWU=S#Rz-HXgY;-tP2ebe>s*~yp8Oa_)Zh07FsUR ztU=2aD`RmGReJWfGwvde{GKNGCR*MzWUsEwayrI&DpFM= zJt}BB5GF+B;y~6|bW#`O0ga|v5rb?A*~A1D_Gkoyop4}KB;rV;i4TxjX>ms6SjdMxbQ~RZ~LGBWBjoZf2~C_ zu(wwS!%M1SGpX2fw9+%#wjU@Dvnz+{RvpOBz0X{k4;d@hv!1Bf?+a!963EZZ}MDG8ZDa$-NoC$|qMM;T3*Tg34qu@fP{#`o}-t9yj%`-dcu^tB$BwF#`fmD|y~Q zopBmVIO|9kC(a{X4R;6>$2Z|un--3y$Yoiv1fwuL#PU zb)<}UrX%QwJNTG+({I7pk2?ltauzfZKge8tToeIfH78~Q;pXfqPL_$5x5U=_b_e|oIR)G8qkH!G*zYvIBCunFI(OnY4$Ig2_ zA;wSo_gxj))rI9ewCIc^LjCX*^Lw*XyIZ3KN0gU$(NaE7nz37SF|jz{H10aek=be> zSj7<;k-W09=V+3UAsq;JliVkKw7)WR&P5(XR=0d<;@y*SHIZ=k*;xqIrMUfUhh$h*&8VYh!CyCSa8>^VYvHqqc7GBy(*O^$SX zn*@Yp5Fl~|_d#&$zIOaO7FN#ax)q4bu0))Pc=q-L#7PCr71OMIXFy?1J;+Bu0o?&? zDBv^VyKV2wWfY3kWJIxP$^Gw+$9R`uSj^SV5zp`6hIuWRGnTQcI<+snvO!d<=@=}2 zcY}ADbeSs(6{!5(lF?-vQwsSwO@LAIK1??wo{%jq?a%4=pIG9udS7E1LzIJIoUC1S z9GeZr*#`e`cBLdx_e#Ii9>wVL9h3q?*hw zfBP8SuiNrBa@)q~D5j8aQ^>jp=&5A;uDOm~oIafwlviweJ_z9hz*vcot!vs18-JBn z3Uq=TBc5L0IrA(Lv7}YbM+Uame`*n@**qtQ-PtFBrC2E{;`{`0&DsTsrw0)>L3tDf zGnoYKs0d`I6=A=DKMQ~T{Wrd~US@qV*81+Y#&WT~FITSi03tdSsjUVn&Qs#u@ArLs zkl4m&R%-(soULC4y9z+&r{3b`%gPm1g_Ve?_g!|W#0n;NIseya_(nN;o#7}4pt$3W zz^avhzNq*clDV?<(V<$H+QP+La=>W1zmx#iCGGRX^6S&uQKlrpG$Koi}uj4#y{RkBjsmm^DO&WrQ4`cJBB9T6F^pcOtDw#|8Lu5%(ty>G-~_phI?R#FKqB*?tug zG&yeMgaW1k5IqpA=97g!CBabbBgMoVdZJf-y{$8q4il|3DIWRKm0!|~z2&v!NCSe% zAUErcMuT4TR~g}EBvG5O_-IfY&tscd4`Bv1N10letqNmUI7v&{3UgNa7;lv(Wcd0Z zq9@%-6QuNRHc8R1QeQM9h#gpChkM;J)VNj9L`tUKXq?FnMnC_xmA%*H(=^XVr0UgW zKY_KIf`j$MTJfx?JVc2_+l%t7YI}Y$%MXYDMn$~6N~u_Zm7M)8luC49r4@+5r+sz|xH*1&(?it)J@hxJLB{OJP}x?>@#rbs!CS+dDHgFi{5AB@-L~03ZNK zL_t*Jm)vKKB#}6Dx)n=#B`1MX#CG~b>R){;{)5|{%*(P{Q?^@$lHjDZi>1e^^AFWH z`|S}-O`JHlVZ;LOI%8~|;NnS5>-KG}zBHxjGoneSj<&m8&Pslg$_t zQ?9VANo{Rf(wxEq?xn1lgHjZt9Z=~2AR9x43dRO4+-C+hX<=l%6XShA6lEF*OV~zt zAd=iPpGjL^m)+g64$iK}s9JI8jRIy-fYW%OAt{rXO8eP~n|a? ztpeJIt?$Kfp*aE2h)yAd*1aAJ$JuCYUm5kUysojw-afVV|WQ>tv*g-a3@n~8mKXT zeYKNs%us)i^9k;uSHy-saV zX&+dVA_o*?zh76q|EpcenZKN)Q4gngZ#TNe17>lwknd)!3MI^}w$4E@{;j_25)~V*7von;Y z5ycZ0olm2p&vi3N9C{>j1no_x{Nc%L%9_I~uW#SptGHd|Ln=XJul}}XrW|AS#TJevpribBQ73s)l>xnJ7)Gz0ynp&Em6D zF3h|rvM~8o@Vh*C(f-W4M2C}yThpsW_oNCXV> z)(8qPA zq}&$??5Zk)ojjVb_N(KmA2sC-tLjp|s;UY^2fPRcfo!7$66A0r`XbN5wqXjE_W4)S z6c7{p*}La@_pfJRk-6R8oCMdv<9@iTX*|nPVq3Gck10P41Qs!GhwwR^EFw%Q{oe6@ zH8buU^9dG*oO6=Lz=QMitQbi*nd7oJmY*bxdpilKOSgAB+_>MqjC_bV=;Pg+Di6P# zXL}NGaqimSPivF+sIw66UiaNT3Q}#Ik~ZD_j^Uv*u9}|HpZSiXiK@$w9*)E70qW6& zIF_R)FBkRkqIUI{pF+}47u~*!s9cX`T~*TtorH!9wCVzvNAcTfDD^sR%li|jSYe#} z+c*Ncnkg?9nw|*0Pk=`|6gI_M+G`tPte^uG_qsg z^+KTQ4}J9?L%-r=#3T9cXm;r8j?H=@Wt=R#^>{`MsS#~#i|3q2=d^=~$g9g6X_r(O zE$%RgzG-xQVk|@6{R+x*TQTEGT}}^{o8!$Dj56^Uv6U-B)HtM6Sf+dB2<{h3Z7;VYZD~ zad73taOuE{c;wHJd0X!;J#8W$&HH;lTq3gCi4X_WY+M4y8$ax5AlL#dOt1$AsTv2{ zqx0m!f!?=VQ}Q!9BB&o@50xgFkvo?1BuwL5R(!Zp-_CV=OUXK2>-6GMsk6Qma)R+r zcJPret(+!Bb&@#tiCl`P3EO?=)9ByG1)`~t=CIE-G>~r&mV&qGTI8YN6{AoQ3rGxn zWop(E4>DE;*17BB;=wFMNJtJKQ^V5HadqRI0ixQEC597>xZi`lz)?j?>cC@}URJtV zW^oMFlV9C)a5`nmk>ni?gW^HIk`c24O$n_}!jnUjs!m9c6uxQNf}Fjet)z%UE1V*P zmq1Jv+P%-cXqss}Ppc~%8Z-fiss+pt>918eQglRLXc?_Szk_-Dj~FVZ$e99dUqT-I zHzu|xPbAf(wU4;qw4(z@i==7;Fdhi+-M@GtU21}uguqM2&oGOE< zRD$;meNDn;9k=rU(Aoyv&cI%ozOOTHtlt*mJ2eOc(QH6IV?PpYU^n&$)0Sy{4AQMhSW|_M=D|=7qc726~unu z?o0r1Umgb4%3R33cUu!13f<);F=7rPft8V+ITN{%zdpK+toPtyr3|Po6w3oEmh9DJ zOR5BFn&9j;<2u`ZH`WF9QKgl(=kUi{k-ll9zd$g!UrweL?D%Sd0B+CNMOU7us;7Jj zt{l#r9VxxB*}jboraKwsI9---c{oo)Q?JOQ0wtARn^)?Rg$EeFXm2oAo*tvs4$E7< z6T|K6YHYOcytFG)i&GElCoifzNFvLxpZ-}jTWIAC(9n6}@J{mrTDGG}o-6Bn)$k{W z_&=3dKW4O!YqHwzMseb2%QtSP)z?1pWqJd*BqHdRECqNhpw3814v1ybhzNb_ka4m zK75mlOcqw=N=2(S+|xm2Q`>M?1>;#c8z_4*Npd%S6WFyIKV22?`_i?-GTai3+LhTl z-Le3>_j4H1LS`=H%B14iDj3;wv8vh?K&@=d;s5i;`=9(Pp3jrDqBK(7Jm1bR0H$^s zu&oNy$zO$8mUfiv1IuL$ckV*P+`cJGH)+6I*h2jy-lOlE=j*xAV2)$BI1&mJu{P8O16^DaM@ev~WAg-hU zW2+rc1>MFteL+7ZL zmWIMd`ibd>z3&|;5heDHEz#96!S*M%FR(vmsG3(ws=24+`PPocO6WPZcm2HYrpHOQ zaxqrv5YY4+H9nxsI>|Q@fROJ6$hg?}HEP7VLWAq)u*V9Zp)+2cXy6PSN3&|kWt~)? zFsbMStHqf#^^N&nlOtS2k3r4xl1a}RGdzt_Dyne_x)H97Hx4uGo(=JkM4`lT#C4nW z7qr%8JdVrDHrA30INzR;e*rc3&ndm`lIbGjM@F&GtLq|1R5)1Y%8BqG=?X2@8Yc4u zv$u4MjMrg8Ih4n)sW)jcg3~%9>eOi^lQSJ*gMMGCSA^r(@|YlTZWHr~RMn=(g@Wed zv>QcYbCx|npOMqm^WG#G@i2+DUXt-ZTZfoE28?#BM8qD?JP$oYb#J@pyWPuemB@Z~ zEMO&T3I+t*s?y+xYJ+bukZUz~8UPo2hn%Z3Y`T%+yQ75$x$=1iaF@<0;hYW0%v}9r zp}R&5H$Krpa%~;z0wDo>K5MP+sdI8Q&#-cqIRj1U0)rWe1`^^HP?6Mimv76d6@UkVW&sFQ^5!ej~8JlFkwfD_7un15kM7EfMWj|RMXXjh3)lkI)#(a(F1(KUlA&hHy5HLXzf zvtzJrgX@_7S)$P5xuN^vJbP^JYfD_k{l-1sJW|ztHWmAIDak;f!>iaf*xf?k&ReL@ z^VA>zz+Zpkra)1s;(lK+i(qXkJBGIzf;u?kOAeIEY%pX)#36VC@k zF@p~WUsrYQ6qLV$k*>@jtm0gmVas|9=Mx0^ej#;IPxMOl8Qr35jSv#KxX?Pipc2lN z*va>zpy@|V!ZVjqti)+xKm!8%Y3UhI*)Y#^k*Z+9P%g)%x)jH5kWH2&rH3p0iT&^Q zB23THWC6N6C$pqAW~kBJp3J9ay5O4WX#6jzFNVrnn|p^p7P+EvM~=nWPWzl0@U081 z9Owg=r;vd7;vbCX9CY%7L2`SqJ0HH`E=^QDAg|JDKlcF?b8`Tv@%CR(to`$^t`reS~=U!e`QE`bA2{BllvzRpsQJA4i zl@JaWkB;gz3$Xd4JTj0d#;=1hQ;u}2^Nv(!EFSKg>s&fMoVtG|<(%q%ArCh@4r1+E z!biMYr=rVvDW!9ssd5ae8Z5al2mB5mH9i^yQtiH;6bzN4%Ts>dt!f?T-5xzlG7(jL z$HTH>jYhiV-+?}0YjdP7eNJ!R=~xj<#cGU-(0My-*L{0omSj*;0i@0CaoHY~w0EQ$ zx)D5bA}xH$$Q4hAJ+Ix!+03=MEA=6g*dpfv3$?$k(Z>kz!ewIG?3s90?!EW^D)Y7b zs7O=`rMK*oE!7JkIjjIthR)K98P!=&cv$rp(&3!78x+LE8UM`O7dLu0FZ{YgP3PpQ z78JTViHNm`k!mb`c8AWl&U&-u%JouygGY}@O=wNZTtoroT^u(quVWj&x4aBLz4`OhaJrJQnOt2u;H2DVk3F-7HYMVpl}O&R|N!(8UTeVhe>A$!>4DOMDrx_D_Dx>e3QN zWCN=Vk*?YExZZty5buV$6=t=rR9Bvd8_QvI%M-`VF(L1-c0TjiEQ66dv@j<^T{Lnz zx-CR>+HhT>;y@Buv0MBZeAFR#;1XX=WPY?6xgsB#ex-6@3_`3Qs-o-QzOoaQBG;e5 z1J#Hg*`8&PLQT3|&zv=y{|j~S_{_k=vI4d(&BJXTxsj9>8}G1KBdXf6s`2Dn^A(do z1DtyIm6r}pJo6#~ijIUV9nthfK#9BK`}k|u=KII5`sc6xCj%&={z|Z8hn}N`>Wv=| z$n_*pRhg2YRMmdJ4R6ksfmlc&cWo_Gs}?dd*R!7Ism&d4KV^xYO=hkv##_}?!EVIS znh_K-8kB_$BB4wsR&cFO)~o&Qs6T_>KlZOb>Q~_t{Ifsste1Q{>tVvxj`$eXq&Rt1 zRQa|blATo$VrY@|#ioY<9`{Tr$S8*=_k|M=cn81tzIE z4XPdQYiPpiF@3CT6BpK+#N@sW)q(Ffjwe*8!pH=dcFVFWY`HZ3CGHk7s?+X4)eaWl z;@i4;yy*^x@b2AIE%YPAcuXf!>H!+|c@nT4i+afmm&OFO!K)Hg7SZIImc#;#Lx@O> z(hUW*Px9mR!gFy7Cs?CS`Baa(xgr*rWVSC%&M&V6=kFc)fc+mK6^WMqb z-9?B=-VEDx;?S+oiLJu=UMJ$Fjla*cPNf5C7O!~qSy2dWd%svg<5m2u}CqV zuo0%^>%}jF{CcU=LM{C|p%vV&6F{YPx&zx|;#BFV?K%j6ir8Oucgw}YI^pLH1Juae#S;PZ4t_R_-S|zaK%*sL`QxqcRI}_nE-;GlzrC8vQh>?YDQqiFEMi^#|oSU zrWraKAT}(R7wTN4Rr9NhRq5d3KEAqql1FAj^(rdmb8bgo#-a0sP)mog-IcHsD@!Ej z?&KTQJ{K?Fi2n8?B5OWd1t=^FCl9$^IBq46f}8Qaai=-22PtK<L;yat8;TKkC3S>Pz+ou4qSdN=A2ik=MDF4W_&3)&s;Y;`$c;UK-RaR zEbJxhAaIWMHJl)-eL@>`v>9(yYuKFphSpZ8zJl9UNf-Q+0N>~g^PeN?FFfhkHy6t5 zv2-)h?bB@4`bXLo+vur6;4q@~1{SCRn3En`D`lX9ET*zPVuY+30aW~0t5W4cOi<2I zm1o3r+1W$Jz1AbluzF9{yFH?l;*O}cm5l7;& zwQ7mUs`K&2OO1k7cv@XfuI+`w0Hf~uTs)`dqr zj4se)Dtq5}N1#Pb4KSCz^m0!WxQ)>c*mK^Un>E1QcfCE`_@!1s0r5INx?>NN5;uW( zR(zalfE8G=9^JmQZ%;*@h4G?oC5h`5HZXvtvY565P^Dxf@hmZHVxr`C1o1%fjei+Y z?>!Z>MgT()=m9cHKjkh2qLYcr!BP`$j1g7|=p-++oKZ#zNdo6k%eC@(uog4xd7k%= zf2{g1zmbu#9socl)^VOHtgDc%i>j*qthI7=1ZmsC8j4x0V!ZFJmgq!~^*oO<-J2hs z*zDbJpI&8VM77j;7m--&0dnuE0%D<4k&0E^E__!a0W$Ymg^cwe@!$97k3SJy$;!=$ z2SCQ_EFOmq%|Kn&hUX-{gzgB3JtNl>@fdBmW(IGFLkF?E3Z=Mzk zBqw=tWCEim==0(H*mI~ro4sz&Cc#zWmxf>r*aSyNqBpCHZDN8_lgC+sPBGMPu~h$Q)YS`|PD$xgtDWlR{8Li^j5I!U zK1Vs~yE#Cg(H zz9ThzRclmv<=ob|>6C6ov83DRFs*7JxDk5>%3E!fMANGWccBN4?8UV~=)ur#Rs&}k z52-2~UM6xJM)Q7QCO!=i+CXVII8GcTzE~gBrEMZ!VsG=?tOx7mlJ|QYk2H>~J9gp7 z{u$WFE5xp6<(!TM+8}UyaNN2@`{8ShyLNPIoTKEN*j9BeSCSgYcz>XE`x_{R*^p&k zQ3JFPu^tT$w14yC(i?@7G%O5(IzMxpIs-=PKVkZtwgh%8sg4!Oj(1xl`lj2PNyr|T7?X5ju&3>Tl)w`4#5tVZ-gGImOL!mp5!#S zg7MZmgDt+XUvUa?&+ZYyN^;i7GaC)mu6m8qg=tkSgd}3EYq5XXOpXy$_RM3cK*o0P zij+HgmKbYTfOJy;j^&ZDGFPT14(TbL)qyxFBuXck7ylV2Wp{j(3s}z)`V7s)yKxF^ z?jXQog&PO41V%}2zDMs1-wG1z$(im|AH@J7K6yCcR4zQ7LCuOT3n!nLQm|%3Du^Ts zIl!H3w5JMo`9R7i7 zzl7EI{)*a`pWRMicN8)B#BQfBc(tZyvHK(Rz=@h;&1loJ94%*CGMD}t+DFuG3I%2K zF_ou`2G02dXY@rKwU}}jkHE~3FrX|6tCoz~ z-5nOe#^llGwMYpg6Ci{1;DWReb~n(d*2fISaHHVWGI#+knj_Ng;+X6*XRjUVKq&G` z$9MiK26F&o9PwKhjA#9bT(S4%(n+!~ju%``9w$z3|q( z$CB4;V2tfKdoxy8Fzc$7T$&z^rn*`<1Qnx zl6!PreQZ}XmcQF`)U%#0g?c2o+WXicJ=sfJYJ)mR-QYs96yDGmPU?KGPIrokg~U>= zCagzoc|d;s1CjO5fB)b9=lEa$9?u{DEmzj_^jVa^$N&rg03ZNKL_t*RH(_tV6DoEd z&N15cnJ4+yB^|V$56|I|piEyIX)!Qk^}%Uk!b0OjZq9g!(e46eX2zW=iDP(!pB@}J zednmNVWHfUrr48CTdI3x?%=(#?kcTK$A>sK?{JTdUX0V;gZ4^ZcewC{0sb z`a*Ec8+?1x?Qx$ONa4e+|959{J}G}kd2h)R<9WOZa~XqIs-_!P{qtAkz7eDIAPGwr zE|deu*5qVU^y&tV93Ler%G6xP)eIPFYI}N8+$DYpz^f@deDC|5_ch~GckQF7bcltM z734`1NL$f5)+0r2Rja1N3UOZGJ3&#yIPofH8Ps+^nxlJd!*F^4-4U(1T^`)fQ>GO^ zqOCIp(~@dQ&lQIG3=fEh7TH_7g&%ZrrIQ`n)6gvXi$l|P=Ap$^I)az7*BXq4a|FRV zyRDj>i7^x!XEIE zK5JNpJFD%Jc#lg`sOBG=ftAX&zVYJ>EVY^!OdDAzs$C6yt726o&6zi}C!W)V0=`}` z;H>f)m50aac$o7@Od@6#RTaozVEb%tv-GqISaiaT_xuX-W z0SYT|o>KzmV;^a^b__VAcUYk__|!``Bp8I=h~Xi&_CZPE^O>ho9ZnO(VoLVh^I!l{ zDy5|~6-siM(-p(^OR+dO0WAlN8o<+sGPrjEjJ@AXGJCkU+FuGdAUkZ7rF;jy(&>i= zfz`V#Hu}DM8DL>=R1vI}#zidF22y|8p%%rJ>Zy9cUk>IG1VB$ty}$qZtpl;u?By_@ z(nfGgYg+a#K64udGwO}e+QHx3HlkSb(Tw+1TC4?U)JiSKiV5-nN(PA1_w-}}#{#y! zyA_`7G&NUPyLksLp7q4Ep#R`XW%-h)Kp4NFG*PLSb6`UG#HLa)B{y>I-j_r4b`0^3>|0c>5;fdXs6P5#CSgTaT!|*4-O*MI-SULu$#@6SfT-1D;~zKW z9fxyfdx*W+Hr*YuK)z4_S*!C}f5-{^GNpWYDk}JJ143nFP4DFUwKuG#yt-NbxQwd7 ze8oAJp9k2;lO3Wz0_0UHlHLe<+NxNr#6s;@0I>HaV$B2XDtJ!{Dhv5kN8_qu;jW#M>v3|7 zR>q3Y{(gTw!TkL{-gvP;3t|@FotfDMYL1fy?aT!1@=g!a!*tXwu$|DhH9T54$ngnq zUby?#V34myZ(dC}iQ(Aui0%gQlhwi@D~ystK7Z|1AbAR;a_?dS^bOicq*xNb`cUmp zT_e9z-c$eLw8#2{=e3q+JF?41kVeeY+SPU(=Mr_6R_?TjHb*T|J)3M(Q}BB{v|&=w z=3xSPuW`_0z35ps0;psm%j(BWg~^qtR669ZQQbI>WvR8eZW*0gHkY0rxc}`^12*cr zjM$J5f0U?kWm!!{HCal56UZ@HgVe5xwV^g|#m#p~>&0>s_$0j=-4W{ZM7gy6cW=rqJpzE~lLx z<}ctZX`}&O?CGi1C{deBVXQs}dVR93UZz8I!FQtrw9aEGRLAN1tdi$=yVG!>w!^^O z#FqoT4Wxl;Rm&tO&Iv6RjRGbV#p=ko`F7(Rq7;^MEV7h=ftV%fQk2{_f9U?s^a14pyq+U-7ff9H3 zJ}8{T9fQ}t-Iy$bDOm@O7*O(-UMC%N@|0pdYax5syjE|8SUZOH?SP`FLNxIc(kJy; zkbK|2_!bJ)OWw>(6uIB4SoD@Khjo;|>WGfjLzCTgr$_0#7ZiPARsdMflPeqL2x&{G zSZZD(fi)&ubJH>6?-XxIB{GkLwh>Bnq?o8h-Me}^Cv{vHEHTt5*cc!)A<9}fY1UhE zD5;|)(`9+4lsVi7FyUUiAcH zIhH`liivS>r4m}>-Dg;kqjb8=hQZ5T<+oDbPwDcl3Ouc&gHy$13u(7uI6bA;j@Cpt zgu|1(s)O^PKh=DWh4Q>dtsbc;#vZe743J!EH)+xh2<;&UEi$K@_91~B1d)?|bypc` zwJwND{|@c9FPCUJB8cz^4Chvx-&FTwo}jE5RrE|$UC)dFw%OwvA+bMCxTg(Al-2Bu zc&X@zt(C!|lBhf)EfXV=?Sc+OtxxQ~er^7(`0jX~Cj(dyR(6jpU|GbjdWX@k?SUTU zmDwG#^Hfo_|9W>-?RUTX8x8#h)>^@+R{TZ;@T{j}i>ruSs`6TUO(YtlVP;NHLYz)r zk~EkBzL61l_!stfKfnI#pZErmZy>5Z0YtpwVf10RUr9hkplQj2Vn0Zami2a&UeiIA z);{K+=LvkQQd^9|*v=K_+@t6myQ6HpJfzdpO>JuzGodq|bik^Mxz%>HCV&KYygcCP z$yO-?;QNm<-nrn`R=CllUdN;#qIzXLVfFzhUTC%8f>+OwLTU+#zpmT_hw=4;aQ5fB zYBPZq07O8$zY|oU0edUY;kjo!P)t<8=#`Yb=wb9q&jPNpyR>xggq3#NdMad9FecWH z$07NaD&ILR$5YUQU8$vV7KS#Y#^nseVS~%J(7haxz7A_r!RD-9m%Uw;l*G;3g2H$l1Hk7uH zpraO?ZS;(^!kNsTVKp#xGGnYV$bJ``Pux^pfme8dymc5Tnv|soBSB=kw!BW5xP9rf zN$|LEj)gI;#3VvpbGNDbDzyaC5Q^xuYMm{q?BtQ6XoA^?(29H_$$XHbopiR-z`f)x z2O?v|HQ=t8Ft%J;na;7Fa|IZi_xds5S(UTqw+~2J)wc2j82qzl}IzpfX5LMSu;V7iqMOHq!)>3e0 zqN1K(t+WS_u@dVU%B!5by$X9vXdk1QgCO77+y9`5Q#U~*XJsumTN3eznZc-d2K2hG zWDX1GrW}VPPa4Aj>=N9EFJkmdX{%3O(mO;XYC}_Fx?Iz0m)evta(iC|kYJLG_iYZX z6iGdwjxDzHYm0oRhjjtBE52kG8BYcfyU}eE=UvJk9C$#9`_dxqJqLVZO*5tq_a@np2u2zF!kjkXL=8n|pU za^Pg493JCpaswo80AqIoXd1N!y@CCht(@78&na1k#yCRX1ne+uU4DzR{Tmlq6FV@= z>ZR3<6vNgPFROMLN8hffj503e4)N0I3`|(1K_=Ub%Ey4mt`&M#*8tLB)$Ut_24eMW z%6{qhqMpd zx0>&zcBH#zG2_(}$t&ru?S-$gfwKq8(Q5QgcypAc@4nJO!ChzJyc|_p5v-ot^1IFV zrw)3ER~hgARz&^v`{(%|{_!`y%7NIjltNEiAtIio9=BTb*1@&(qa&!_(4 z=l}Zu@K-E7%bBbMn5#E1%SoZm?BR~u_->iE<%)Svx@IngaEfZ%bxE7!ptzukO?j&^ zZ=|cJU6Ek#LnL%cu9H#ab6)Ye(CVgUfsYO36B)tRddm?g?E@l4b7d_D?Pnra)JFT; zi-#uNV(Ffe$Z*~isMaNYd;vu)unEbo4v9>0fQos=NWwbRx)!!N7;&g|kBU3|Y$+xcrpio#Jg`fA|t=blG-!Zce*0}SRwaf?Fk+G7dMIpmaK|U$gn>Y=mYy2*jaVO_J(RsgxD5s~zApPt zaPd|zq#0991cO@59MOIcEBvP>XuuP8oYd$JV$dG8aXxgAA;XZNevtYr-i0!kv0J_j zJlZ?T)Ow-^A+$IV@pzkxXy-!UFo!3o$!q76IcM1^dTgDH?;9I9F3yxiNXb|XmYoySmCgG$O?uIf9o#Dd7kZH2Q`lzM=B@wuMZ-Ky1r^281j zQ(*OaK#;?Gu$PEaKNW*d6fe)Y~)Qv-s5co{A8MtMcFf~LD~Lv&kpkfsh6 z$=ZpGie2RHJZ|sj{p450e&41MH7kSA@BnYwB2Ly@`;Ces(eCdKOp0Owv0|5(snT}# zQUjSQS4W@pCsb8Y_1w|S6RbyB^GPo}bvRENZL>jAFIt0|nsXQoY{oBdLo}JWpXpT+LZ>}oVHp@Fs-%vO*L-H3GGHL7=Cc{= zIW&m4!#vuD>EQys1VEE9<;dRx3^s`x*WVD1n(T=_l6kRSn1_?hiE!!;Klru!z9D2A z5pV2o8_4x*mUR`4JrARpSTm?>=>hUeF)bq#nxa2pEt7Abg%fkxM~cV!PMDJ9=>$|x zF*FugzmP(SJ`HHGYBbSM4J)V)GjryG(P;X#aJRJO&vmA1Zl zcVl}H$at@1a=#kJwDd~FP#*&d`=pCR`kCQ1-@e;;|0nJ5HL%2B?X}eQqy>QZ*uvZa z9)+jlOdH*?W>l=n!kKC@#y=_v`r}K{oS`1%(jmz>1p}Us3lSb?^)%`uO)T0jDm6%d zX%7&IU!Ujizfj+Q=KDr{V;5^nG(&bmyO9miIOojO$7{X)E*DGcBynfN#Ldy;7R*EwpWWv!MhlB8nT$@@wjK-`mKl@BV)GZ@>ThH~+$l z^=Z+X_p7^|wynDie9qh4M9T|?v=FYMnTm9%toAvGQJRbsp^XgH)X?2)jF$5q zDQs(rdqUKk)!8{~`H;ul)d7`iL7T2O9T%_l;QScpIWdqX#pk=Ve-$Ujb2VPE9iZKM zb{IkX9+wuLJ=STblVvsVn{_3?+VDTU*7#ZSy4{bjKCl+j%o{j{B+Rnv0J+)zu_CK8 z+9XBkt$Y7ct@NRjYz~nsPh&7ayWPAk2cb`>TO1^ra_J))m*t>HOmfDRGGKY-uoaqe zH%MV2z$P!CPh2CNxaX8+;xuVuK6_%)sb=j*jmr+F$hRp-7Ag*_HcFaz;dv`IwS3EY z^$_)H!@SAu3ZVm>auR@avh=;eV;n8j&?g5Nqg@A={)0zZ9;~Z0Z=>j2(I;ouNI5mw zws=9Ov{yQ<)9WX#`?lN|g6linpYs2M|?2O!7-jn&H~d zJ3SWo1gWOWA?zmy2xi@{M;ThXLb^y>PRXk|%qtO^KiJ(1)@l1_8uA5gB~YXqBUa@a zIvrIoa7YV!yWcvD)?}oEruNIM;~IM&?Ol&Rrq66=J7XdhFzZzPU_HOvJj=aY>J<~3 zh^;jOb$6<*hmr*hN6qWdwGPfnL}lzeDm;2aV^7UVIB#i#)=rFF@%@Hpb>n|@#MD-0 zk$IBFY@U|f{0kmU0rpQ!rwOin{zVGnE|!03eQ?B6>s zBNET!5u=q!C#}2N+d5LHier?QQA%kvu@$nzy7&aua#NmWMhpF|QDxPEKliu#N=p)f zhH7I0`2-&2IFC)DXDPOD=-N=dP6Y|Xm7^UUb|)0L@3R%?2IK$l>il|bOOB+TnYH%q zZrfs*5pR6|uZ4toUDOLUaKnqMEt^sotq8O5o6XJ z@SPnJ73dC#_QHW1zuh>BB=$%p6Udg6~pxh)2z|?xj_|mqA zL+O|`38>oivesqa5qE;?#P<>I!cjsbRO5PQIKL6^__U&DGO`0=fa}0oTG{oEq|vUg z(9M+u9(GgvVa&yjZ6)OPc{;z&NDL;%3ARkzZFxjuEc<-Ps`%J5WdL}6I!Hic{&ofc zg$cNbV&9MsC5NYG^XphFc*1V0j$zE0%=EApE-x0;1$aRRb-Ua!G=d)O?o)yz&PH7@ zmA-=VIvq*mtgo?ZwRNOWb0tGsUWp>D*>pC!huWxgo(Sv8SefKbOkF;xxU>!;Z8{=W zdNPxDn}3%UO}O}283!B@N(B^^)FP3$<0}*W+i$Dz?@>TzM?AS!6sD^wG++PB1gHon z5SmIEZiUxLhRdoo1~aK?AmH=LQWgNWa8+o73b*)EY_nO9R@AChncZLAKOI0Wp`nuH z(5UA@eDIaI{`rsk+b#(3zO~M6!y(VGsMLinYe6U4M->k}FL?rdCk&9QmUmlusrP*r zRymvhX;c?~$;LrQcZ%-2D#^YHz7au%;5w4GzJn& z`jl+6bQ~n>NO%I93H4rL${`myVf{35OhI`ttc$F*<00gU_OBnoAqU>xt;X`;* z=&)5+Prg?QdKRm|m3!}aw+)F3?#j92fRlyU*8qAJVAG;cKPg-O!0nZ#lmld$UHz0A zZ7o||g-3bF;s}YHuVWxyv#T&W-S#M-y~9S)ng?3aXmX0_pkhmcF5!&*Wz@KHW-7Bx=C@Spo}ihGz#5K~R}_rPRMdlMdR0Y< z&B_Ee%0@ln#kPFTMI9G3Q>t8*&&qP`kXhA6?;WO+%!=W0VD{(%I3en}=6IJS>ZCzN+86nf@vaawWCcou&t{NNc z4?L30vai|#OKIvUa@bQDIFtGg_mfD2kS+OIwPqjaqHbTdk%gJsURrU796ABKa(q)N zk7&fbb~fHBSS(RFE&}uYW?2__aexgRXU@^2E07{)cwwl4hq}{KlP!HXlc?^$H|5AW z$P&6WpyvFB1lp|MuWUIJsnt{3#+5v@ILK7A!@jEOYpkWfx>-s%P9u}?vxRT-Ip64$ zxi|N9d4=T&+XJAjI9YI&ak6H0tYBy>gW*L6&%q;5fsL}q;c<35n9Z=}>)IiIlg~{Z zIqRVBQHo2k9uECZCawpw4x$d&Qa%9>k{&J7oPeica7sszr;6%R`MGup$A)hwkPV#+ zN+jA=WSxjLp>8?{%n;vV%%}p8J$w0o_ot{Iv7SQz_Uq?Q{br!!;c>Bd*&bmefAsDx zE=gdmrA#qN@SIk{cB#rlN~up-bXToNl4-Jz7Dwhra>{^s5qs}IHYi8wHpe)+8KJYg zVLdimaM_90^Ur_$*vgvPco=oFIv&EG9PG8v);%L)Ya-d+TC5V+H01|Q2Fb&j5^SEM z)TZVE#<(VMbZ6t7%s9aSs)u@ng_jqPhh#F7W5zv;ngp>8sh60zJrz26hFKE_daD17 zTqmfBG8r~nl?=zSlJJ_RKqVecgZ1#iQKL`ZtjkrNZJK& zSPqicsC=p0o~848#0cAiVA#{)gdk0>+)dG{v44GK9y(A@Q(LUqkM&f9K>ZW@O-S zm7Vm{-Nqm>z3nu6XaK1Q&~+J5mEMFFN1wMj?3_Q8?4chk4+*0KOWpH<+>yikn{L2dhz?9TRbz)>bvxVd+kWC7SwX!1Sf zinZNVaT27WYr3aItiW=uvb=g#CM`x%B>&wy7*RMyrLO6Zipj`IP%csHLMUI)c9k0- z+gU44PaAzIM((cAXKQk#YNPTvgAyuo&-W|Cg113K4Vy2b_W|OLVcQ-VyAK>Da%^sS z?=>-|0O~#FZWT5)!Ph~QI9xwJa@2@tb*nc)001BWNkl0#p4Y}E3gIP zDOUC_!aV6V5KMzh9>8nW=M$NgOG85q;rt8l9ebY<>`D-J#ov{ph;G|ZL(!bPL@Tpw zON)!&pjpKl>G?st0#TJuT|&){1#t$b#VV&$>Py{MTneHpm$$6^yK`ERtxtoacy)1o zhGE)2470=t@md=E6zW;J9%w|-0kH#HnLR#nz+ZpOu7mY~tCMY7i#SP;?{53pDuYKS z{3&N3{Qg7_He!bupn@bvwC7>qEBYh$b)Lcxot$#_CcJ79WAJa zQhq}`S3;^?n0RlS1E3V@qaw~(G#(_?S(P*?WmKePUhpb@b$pPRAT^$}I7wZT z{ezB65ikJ*#J>@Y(YTLbkBrQ(YE%LPBcb0pH8cr_73HK9#tBDlG21c*OO&(Um5pbn zD%{D`k|}p~xs-C%C1VqkkV=7-xv-HnA;Lqxe;|2(MRxN@$;b2E zAp_49yQABtrn5msd%?BwtwN3OxSdoGre=#7M_8fuNhbV+b0av+ro}Gd;%`d=TY2W$ zd|<+cF1xuTPScWBrz9X>Ow7=2pR5t96I1c0SMM#8#r(i-WQ*e+Gs5gGV zZ~NN<=%jg2Q|&P;HhZ4$?G zYMT^pJf7sB1oK*uithkR@PLWzE|tRS^k#sfuqFK>d5zV|+J*M;NVO@SUr%GagPQ@ST`LNe`PRvDKW5l>!;faC)}p3*vL;Y!CF zMk_a{Qdv1!D$L8&y)lSKYdA@N;Xk6-`xtqziG$`{5b#pTi!>U{ja0TYuNbVz&}RCA zB=H-pPy|Nzsl|C>6efD-ly-FmF?4*}6=S9_kyXL1S1pRrU>ui{J~OdH&I1Yh9I6s$ zC6#PxsEX?Cw9>@_*L)berr9r%Oy01d9moDKYN=6Xg$G;ZjTUese=o^mGzZt#d_kI zc^3Ru<;~r6WzsHmEZms1PCB)oI@V%ONw^8;wo41xvU6Tk6f^wTz0|O#XbOX%RKq24 zVEMW6Gvax2#aZwSR7m==PMsLgDOz?Pj3T}2eDxC}zwPC$MOKm)zcKt5ciKIDFK0Jf zuAInx5=HYlVrp6*LU4nyCrNZdWFa;O&F6FpElpN(dFy^WGCXp(s?gyt=w9f&hFMup zocYBhP%_9Jlu3dOKmAJd`lkQJ<1)dw-RERwb!Trx$3i?4ZAb~VqV}LDqeKZ~7?qDu zJ6;oXZd?ohCJ^&N%1KXGR1?CQ@o039y{>apo%gjZy;CstLR2h#p<7JT9UY~Uq!H_r zxpttUIx}NcGBKvCn{~XH(dE`p4ed}l^-C_7%)r9oHeYj&Avy$(w5DIb2E=N6Xq2vw za=2evd=(iBISP|}OwR+h%?y-Pr{N7AO$*6mWlKIw zE`<~2_e2)$aLKFX?Xy3bWFyWo`qFOo2EMc}m6OW7o1+Czv)X3WOoJ+7N2GW~p85k< zmX)`ZlOK>4kavIcQ^A)JL44a+rrj(jh1@)Mkda#s4hej-=V`^N(Lk1te9jm~pOMVHtZ%ntqs&^E*b8dYT#ed?R4RJo9$=*q{?LdX9>& z=#o&!X}Xp=;}B~Cna`B(_qB3BvZPz>yz~xn&)EE5N_PuH){`ghsO&!+ zAgD}BhHxa}L^s4)C0s(y-lipSmIaLgxTw*<^4bIRhyr+EcFq7{xcq%Gq#bLVS>h`y z`6C=@K&e<7d&?(tL-wce(yz1D1gl!}HpjhV)4T+W!q?hwyqWJG|MW}!rT^N9UC(}Z zM6VSU2`1pN&FetNZbZi39kHACV5R63fZi=F$kOt;HzKeqb3Kz{F{1swH8AV$ih9=b zmzsPw7Tz1df-s8tyamis^7-9Tnn)L*-P|5+d*6A>>n@4j$g85u8^ zM>D}?VQN?KCNAXy@bbj?6gp&B;=qx%%(xL6W(On!o3p}GOmI?uP6dNB*0Q{x%}6`q ztF=!!^b{)?^X}9*ui241hD$jDT8UbB0I}cbSI9@;qu-(9-D-*Z4T8ECCe40;d6#q8S}O5Z^602zCw)-!8x`>3j%!DE^sC~S9ec+C zxeHfQ5+WIYr7e2cn>|9o!6vCk>gb5gQqNZEr=}0Pq|PBf+w=wwf{j%gHzZK>{yFuk z`-=KVXbj>qBiFg|9tVc+zP2dZvSM$ciRzP|C!QsynwnBX_3-|M9zt2F|=$S8OEW-4LdZzoQr{;*;6Y4H}f-MXpTC zq3Bmz$Fq@3^h6zOJb7waGu%$!o?#FZwo`2OQ%tvhau$z z!w`1HyNOrjwO4yshbXGrZWHS$-a_*WJ+kg zUK%(a>axBBoXhJRO`+3<8Dn(m#iQ$DG|{hj>C||*3ME$>SMf_{i3|YXKmaJQc(}J>=~>PI+Y=a@a?}XV?^w_FpBVa zFf%*$yQ4RmzMNFQ?~cqwZAv^7Rgb3($VJDmT6h&IiQO*(Ydz7?y}9ottXqlCVr}Kj zyV@on`S4a&=93>Xu}~Fi*}#+zJzG{8CB~b&Bu*gxk~Q=m!F}qPXh-Hy|3%Eb@WmVI z4a%ca3s$!eaV0Cqe2CUp6f(f{yu4hRTtAMUWYVrU)>=7SoO~YJmi#ZKucJBBTIP7^ zc1dfxfR0??&-pwjLn7+5g71!QV2}CJZe%m}6+UE4pGtEnehJ*xsm*pyS7c=@U49*B zWOx*DArB-SDFba5*z?Q?Ay_3w;^m8VpUmq?S{kX|vEHr7iZ`MtNTzvpeha<_=sCM|DAM>!TN-Eq!(8K&wKIl!qPu7RF#6DUX@uyxZXVsE_rIEBJlxM{(BDiSo7 zZ!IDU5@+BNX_h$MCN}Tk&K2;Hp_*)~R+v4u9ibQp9kaEw0)^d5@0>xy#6GP@sYNVJ z_W66W($hp`Jc^4pe;^~iga`ssg8%b9E`jWctmti+0liNfLlW90BTB?+KT7v&mQH;1 z_CtGs?JD?W6qgr?VnfnoVf9|Csw&WxkDyLf*uG zhufC*Bv$2r|J%FkL)v5($p@UY?qdO+CPbRf39*Q%g@_=~AqnCVO*AL@R{c_8ATCsw z6P0g?W)@CPm^QoO*G06ki2RYB-ozH<$=B6x1coW_AYI3+PcrkkMmKZdbM&{GydbZ` z$|oMB$midq)76>9b~J65&1EC5l@8!^r?kQnpCu}Kd5l}b^VchIObinjd}We%$V_+y zJ`fm!7mA+ZbWEu*Zztbqg3a=B4#9Mj0QwEuc^qu3N*W`T4w_y4!*|3R#IangjkBBN zsFymR=SK)>x&vFHr*~Vdi(ufSTP!c0hqvF_&*-P}1RU*9tJ1q)b(r!njf|eQhH|pU zDm6n8KIZS6JCC&0*-`%EdGo^}SLBn-bB1*dnDiI_Em|A)iFGNrksS^ln0yM;7Afm% z^Z9e-ddMm9E_(K;Ftj{#P!xUk33`517LOvjrDbj77`~|-Lq~4qHT;A}Joc(E!Eulo zxsos7^N8+2qsP(r&P)KV2jKl>=Lt>a!_y9Y9rIlx7L3T67(A`5JJ6eX?zws+jbO-38VZYuLakA9ckIs1wm2dm>9jKw zUC(;b2<%oOKr>#o`6Dy69lI_fK-vhvROpSZQ4}JPPh=t1s`{{FX&3oB;!Ed7zp)W} z3zq19*E*#Mq;j#%+~=k?V8f!j%xedcMic5lse>bKj%58psU^#NIsCs=l+-h{s(BaN zFn8AV3aO71p5#%bniJH~IporQiLDoqS+VS9WwWFT;1D(`Xh{mfkKj$M{^>krpST8n z90Ikis^PpGDV6&u<w;k?3(Su+^-IujsupERFZ)=&WgxI zb+%n$GZ67nnuz68z%VLf*c(rtpaOqlnIpo)DK(_}n=_yI;&Q#27ZY)edCW0#PG{6b z)A7ib3KqCB+n#7pPe?|ivZJi;eLIFPCCrc3P}!DWYDDsNlJ3%RnzU-Ka(PFxh1=82 zTx9J`9SLyjNJ(48-;sW3=fH^i!Vs_JX!}|9ggAHF-yOp?k$>ZY3u~5iv-+$)paC(} z;iCXJ6{kK@4eE;%G*V{~{OXMiKD15JsA`=z6_m>XmjEPd;djmVO z_NK{|A@}0+kRG_$*kHgz%Fse!cLU81nU7yNMV1UMC3Vta72vL0yazI2P^f%+NA6#K zt^fJ2zrVlyNfhkrDDIZtfg?~~irul967&tsCW#7%*~8m&l?EqjXgWs2wuO{QY%=0d zL)VIN|FDRmK`YCZy&whkcs0b;0Vnt*6!dkVp-+m^@nrP8_~2d|7q(!CXd{{H`G?mI zlRp*etTk?lFJ@ClS&+}X@jR2K^36w(r#T#}v#&1EB_bgZ7q7EUbOPVH5fv_d08xl| z!`A3J2_=(X>Wx-#F>c(0T?4Z_e{67}pSa}sG|g4%dVi;iMxK?_uu|1=?Zd9yuywPp znBXgumMuN<=;v{7c=Ff_qg=`OY?3s?NF=6=>^C_YrHvx*R@NrfqTtX^)TRO0Ij&Ww za!RJWMC3YS`n|6K=p}H&JO-EQTbo`A{p~OumdczYKKFbPFn7v3`G&^|l;ub2Exy)n zM_6mP_sUoc&ukY{t)P5ZdE$96I(d^Lja<1}DXT@6IY!&AgfPr(XtY!bYnE}~75K&o z=%Wrxf6Iq00!aet0PMcZagm!HsY{u4v3bS>abzGDyEqj^3i&YRRYd^th4|VEHKY@-@D4wlgInd$zdG+o8_Bn6{ zoX$E{%Y_y+=)G2smwfD?#kuREV`90=stHEE$?K_MGOUHJ+a_C=0=v!@(F{X_bpmwl z@(E;qc;Ji$!wx~ZpS0H*nrY^mwxTdEZ~LK@c455zbw&@vFW71?u32HnkrNE2G>_w# z<1o!28sf9I@S42H_>O*_m4w%JoU_XkmbvA8xDNi0xY`Htzhrs?4*&9^hkp z!uwQ69)a_D%2)DcUSE-*NR`yds^l|mtNkMAg?cQa8STlmGt~L)=v}BW9aeD>mj-#2 zdX^I^!HKec1#2-$i^)2hd1-{FNL923fKAP+HaZEYx0d3)db@0T1Tw|$Sw?LD*sCIQ zM*^H|!G255v`6Ly2PC8+Hs#iO!gyjjC{Dpw4%uvq?>hk!<1-w+fv8bAGEvqVGs)of zZ#SGWgn{uvrQQ)&54b3Tab)yEF;JZc>RQGqW!uOcd1S_-$;S9lVHxsO|rg+ z!%-8|`~8Hy#msR76-eNW19_e>JZW(3r-_R!GmI7nnph#w0Mr!~bTo3F)5mZmUPgZq3e!Duv7RglRy z)gTW6l#T1W_Uh7dDl*M2#PTYuZr*9unYPB~qUp4iax=%-(=%+~`blWX%i$kRG+0_!Vf-%2@Dy)^OhEQC| zDBu990=rXFLT(6Td*FM{kpeBgjg9V%wD-2iO?avnFGVeLsS}FQKLnHSh}KznpWgrS zZ~gl({}k(4>t_}>hd-?hr!y)|XD#!ASTBABzH~SgPLM`84un~MBKcigwIjgsvLdh1 za>i^gbywZ5LnIiO+^#z}_iOx!_JIfv=U-rTkFH$(WZJ*J>stniJn93v$fXaZWID=fbzVKW!O};#N8Zi6gyvP}M zb#9!bVp{vO3URw?2V*nT89qomy3KRa9gRV90!Q^XwzqwNw8yrsYocRvlB1$`4^n&{-N$_b@7zZW`a(B+4}taX}Oez$HS zdl1f8>Kr4}J?Ak%;>uZ-XTnzuzq?hZv5!8{dXWrhJN|s##m3;1x1vLDyH;7c5lM{U zqPVz2n1I;<3VM9@Sx1qsc9(k{T!(-sUvPT)FpW>g8z*{m>g2rAed1aXr)wD5?74^c zxJ^RVb{jk_p>mOVPg3Gag>X>Woxw{KEABa6o>}MI_}$ZnA+}n?S1+9kQzdEB)U)*= z2sg;4am%@S23ptBe%|H&UTuyhU&A;nnR{X*=fB;m!CawPN7z^0*pA9hX@E1Su5ye* z&CtpXNNSkN%-isar7nzCUnrUG$ zYFNgoMW*he%x9Fd$7St|;&W_L0J>wXsO7J(D#1v4t|j1sBoCZ-$D7EUiGF34qu0b& z&H%U%Ks4Ts-dv5bUsk)+iM2NNOI*$jNw|)*F%}e&4I|2_qF#B-`R`Uy*&FYx^*(>vRY$n~^1Z{E_>gKJ zp7iHTnv%xa@8Hvn=Oa7c8qXmTHD*=NIhK!xcif_Gi5<%cbBK-H@gs8o4fqXFKl1B8 z{^&p6z4L=c<9++hjg^I}&ehtwaAQyiynFBd;x?JyKn1})D>_&~Gjb(Y(XyUd*jSDf zX7^e?w!5QhwQ-`%zwEs`kn2h4GpWm65l#fJM&WH>XBDCv|MmaIU-P%f%zn|ebLIZq zjPYiFu4Vc_=6Q?n#(u54c@MH%5r2`T(fA35*nQAJ>KON+p|M|gzlb&aRho`AUp~sy zGWs|?IS8yNF^_c^u(Co(g=Qp`+xv>5lKk54!pl-VgXojW%kGJfHz{-lO{Q23ttYA? zKFR?u78w*sOddEHR}=4PKUpJl8>N#8#JfSu44sX~DZ}B>$}oU5>ymKeAcb$RSC8@1 zGtFAxa%lMm_9cqu+@iC+*ap2a&wNAOd*KDR<%8HZ85H)w|39lNnP2(yBV zMkjuZZ^yf%qn@1I6(ubv@M7qO{A8#qK_2iJrDm(X#hYuf(|93O;mV_7m~kzpkhnOA zwc>d$D+))VJBZbcOYKjeExj1q6DCx|0|&*a>3|n65&ibzV27iHOkov~TX^2(L36}l z#AYskYQq(o*e{bYr8Vx3hQOV+pqmW6RNYA_YEAt6_ z6xVEBgDer|mAsm0YqWmt%&SLCq>iX3majEmt0&mUuObW;9T;6zIWT3d+;4ns_oyW! zRkN4bKEY?T%ZK)n8h_(k3T7Cs$0ol$8`AgUpK{a_nFngkaO6xxXSa%vIkJxAvNODn zPOBJ-KTl&{;Spjxm(xre7i#{X^5J-wLCQZHka-fy1`lXl`$VY zCD?0aARBmP_#E7zW3C7#K2y{(wH%E<0Ep#^T~S5sn%x8xgZAF=}9hdp0b(D z>>b|x44S)^w;dT+X3i(lGAM0Swm#$<|KQ`4$1^e1yh*_s_uSA_1vxKFIZK5ZYQ0x} zuSX_B6wbB?Ld)c>a=P~c8&jKh)|M-B!JSl=VpASt=eya$HK#BYk?$LyMGbRh;w9Ro zSL8zWXhY=ijjmi>$ck(h&#SRBYh`xtU8@TC;!*(7x%6!ovh(u@iPSN4y$IrFcU56U zeM%MUVxiUfgwhTqd!}`*)E23r7kW#ZrL2rMP!^-NB zE6&FUXHG^HQW>jQbsf;B@z$hDFP7g99ck8GXO$za=2QQyk5#2;~q!J0>4M z|6Be^muMP_m!khFuFMsb{EfPV)x7pL0XR4e3~WalxY4c!V(lb%WgaHifG?tRJ$`&6 za$$c-yw8qe@AURNlZ_BikxPjqd1}yzUC7SK_38Zj{K)s`uU~)r&%f^d{*j;0pMG}s z{`&D-0M$Q##*bvVUqwU9`h3v+)YH-ZLa}yKG$nHPleq&tR5nnV``r)6Owb}Vvk=*P zKWk}9T+X;}R0JyZ-~^+eP*uR@XSB%CjNO4ZR|Qt|8>sc<{`zh8|M>a&e^LGXSik>) zAJI|ud@@S0Va-bmg9cIiNuliXI3zP&d}OShlLb0vjpb3ahRWhLUE=f%q+%FW#0Y&= z&v9+V2b{4e%XQ)QTdLC!000{cNklDHk3N%ojdhri4DU5)+UwjFY~o}~q$C@$ z5+>ZHEOuEd_71!nuI@y=dVg0&S2Xk=IDz2PK6QrPi)9@+6oAKl4}VAwyp{(6bUNe& zle#Uej3;&R-H!D((k3T5p(x(rj663zL{x0FJ^uw|?REB?H6p@)RDuseBRa=Ngn z#OthU08x;nFuh^sxy^2{)AT6@3F<^ke*5i@ZoGDDd8a-3E3i?oPbS}vi_zV3dwz93 z6M*FBupKjAl@lzPk-OvbgVSVU7h_X?k<6^l0_w_dyV+!#k-Sll#6E?1XZYS3>sU9> zKwQ|g+;7&BfSFLJ?dwlWd+*jO5wC7@^qGz04Tz#Qq)CdsDG*M=&nZpp&f>>?NwnHp zr2Y4{%RtI^fbq=t1R$X!&!vPk{$)=^~ajc1MFS;*x!k!!*-s-ia+U1|b5RaESb<^7&UekGITs>yg4M&#WJ zg4vLvP^HBL=Nb^DX-~&2gb3zeij#24jm^MpTvg!sVO}Zuy3mwKf^aPTm9d%>HfuL< zQWbJ6@uBZXMj|?vikzD?bQ((N^s5o3509R-5))$*{-9z@`X^d-v(w2TM}`dPa%wPz zK{)$`L?)*s>#Tf+Q&6u=2Wjvs%&61qegQjkTePTPcd@dtEe&tUQn;=_C)f5g3*gS; z@=E$y@fCyt@p^J7B2UGCKe0RSnFW>&{xbO96G9ic3Y=1qGD}M_^L1UPT8{Eh{Y&~=@OSh z8W*sy+-fPDQzb|domDzDk~{nX2JEOOP?^&Ym$F-UGO`t$$E}%o&l)_Jaoy#}ll-g?Wy6k1I^g z0$YNu;<&&%#YMBSpgfu4W@$xi`l4=0o}b?GrmkiM2a<1#s5WFhv5UziyfSk674OIe zUwdVRpl#y4gX5b}W&I zj(qAz#9#mE*I%E0zq%?q`UMuyPwY>{&O$fm=Cx8kTxKBGsw@rzS-Mnn!=Ey&5U3?p zb5XP$dE}P)?p>=I*uCxWjz+}Gu|*|>`3OWsz1fLwR1ibdl4@{{i2SU6qw?SC^RHi@ zcz#5`jn&jUkW9gaOeyv-NA7rXi4@mP%^vpV=)@XbT?aY#+6}A;>ggzn8nty?acSuV zx*R}AWX6X}2HtvS4aMS2O>$gA#_U<@BArw`Yey>R)yeFr4;yS5OAZxds$`uZu{LK*)vnQMED}BZ)(A}- zD2G+@T=vk*p}PPwOa$b?5-l}26Ehsoz?aj`mv-Wap4`!{>uM$^sWQfT%J+;BYKbGv z3-6$!d4`PIAqvqzMnQwDvGRK=sm2^RAh-e09(<@XH239%|755&nN_# zOnLcK1A5~!vHPIE+fed8ZwhUM;6+-_uV!gCMno1U8?kU?y(--K&Zmc-Ax5Qic_t)t zdl5%}tB;fx(|!4dCflu)M4gi+ApyS!;maVRGghE8b7|eD7-2HaINEp>UUfTO1Q}`V za^aQ%PE zpGtl^-DekFBhAm_4Bzm4`NC0Q=YgWxik6NA$vVQEnEkK|9?_&=Z}J8E*llQ&F@BEC z67x(;?#gA^%P7di(2mOssog_{cY8`FG|`F~8Ima>kh#hjiwFk0a%h-KMACU=4if3C z!2k`)`3oi`a{a=mE%T;Rs(G({IM!LA#(PcpwXNkRE#skpj zzdO3$sucyT(R9|An;!At%Nc5o6G7AQ0^K{33}_NrN}7a5FP8r0dnjyf}fvdzx-aU7rTt+!zn2;lA_MglITao1D+Ls?V?y30@ zCJ01{7q;de#ac{0+Y!Y$2i{@AIEJn`P{DjG->N(rNM-Zw0h!f)eJOwOgvnDK<-)K7LI ztvfXfoDf(d71Vh8r3N;24F?aFupjb7|72JvzsICAA$ zpl+O{%d*~b5VTyQ9Zj9WD&tU0^G2g*4QJnH^yBEV%#%zr5fS-BuAs_vd@MK{4lNQJ z*e?bwB;|r)JoEkc|Mchhk&O+FeI(l&k8UE9D&+~_0T%@{M|1|5FvlSQTGQp&*b&ja z+huVFnGk|Vr*%Wijzl{UfZh6&%^34YlArmaDQq@?xAAk=rvp#?d@BF;{N=y=ub=(- zCGc7-vF_wpc?Gi6M~T|a*WD>q9EFLQwA4I+nXQvwrTA3qsV>wL*P-v$32@)gVH)k* z@xB^f9*;={r~|B2nLB#;+e=TxfX7U)cXyi`p#(Wtxr1xu_jQMW%aPhqqBz+<^Yo>siibN*fuhNgx-NGB@o;4! zr6R_e34L_uM6EdX;NcMnd?+|DM#goR0=K0NhyGo8#ve-Td{}d1b3ag~A?U%*0f3!+ z8VuXPj%(rSjCl3-?9}TxfTfdqLKY(i0v<;eaHGd84r|>S%K;gweB9rwYn; zH=m|@U?#6(68lSxvz)Mm&4?<`%HsgZZ3Hl-p7lr>aYmVXw9{!kWSnb4H-%#r@m_~m zL?Yf+a$XOnbj6@A$F$FeWz~iBPvNHZsIM~t(J|t+7RILA1CG+QJ{a(H1#*v!Y7$Z| zasivB+851!Fh;h6mB%^?14GQ$)1FwtIS>T&dhBG8DfN_o!1l4&T^s4U2M*aA;Ysum z^#*I#I+S=3q=HxDyx{Ev`LHFu6u~|uqkC6O**qG6s2*$4#%syn3|{~@iMij7%qscq zqB3h`t;)qCqB1f&t0?QsT-*du4*&(lkoMit9LjQv=MMgWRKZ5(eWN>2QJ;}g^l?;E zpc0;)40`8qE&yBy%68_;#H{OE?_k`pigL$crm4-DO1?^42&OOMS5k&PYwg}B1N8KT zZNv0Ltw1~{QK>XjyCP0+>iXrP(Pdn=i1g`XFs$J869CDEhx(t_WKo{3biU#UU2Y~|;`Gmc=Pj2Y+nWB^VubQIjfMjwzVs3~tYy{7huk>O znyiy3Q_2SqrPr7O;#%`)B8CWZrw;Jvu4}`p_oxEQ$&1K>JSdarmYLr0d&%#OlC!UG z4$Zj&NuS=N16g)w37!YwTQ`|F8LoB|OCr5i@V_Kw$W!_Zl3T{sZM z3hUe1I2DFkM|m1mA9A3M6}{r?_kaAYe#2LEcX0j!MXtZwpb9LxHPbAI#~ zS48tgGdU2+j*9;z8I{CBMIr9ObPA$Si)j_LmN2oo0@c7%PmXmb4&(ZXLg()Kpr5t& zumAAh{`G6GUn4v6HB|BrP{n}@TBhpC{N#38uN5fkfotkc(oG9%#ff(xZhwJM5LH*4 zpbM^~(yXEaTsy1w-hnU6-Q{SprEP|!-ox;JQH~5*vNOS;`y4XOJW;Tkde!L!DgvD^ z^g8Flq=P8+mm3qUtmTS?_l;imYS+YPvg$h~@YE~r6-5(KQ+R4hPw`6i2D;s!IQ3+j zOa-w_Q0I_Nmtin7cj!xdG2vv!+LMp4jYQyrk#Hk(gRagzwSZmXIHEYtvoSB>5NTJK za8OO3s!NJlo3O8kvu$A8DnfEOD5H)7c70BBHUBN_l4T#LXrig%z7ud^hj}>2vTnyI8g#R7Q|YIZ!2jzb~*;P?B;E zlMQxaJhzk&FaC|-+3NYfh@;F!x+&KGEmZ9UNRNa1NT7_AEA>= zxtIYw2~Lfy1Dt!yANhk|1IwA zoh`v}kc!*=6i%A7Qw+9XOiFT%3oR5^^J3fHpr+$h609mQp*B4y{Er|4T&`o+c~3lBB*LA1Ue?M0J%9kZXXmc68wIsqNR{)ZW+*(y$i#jm>ca_g%UB z%M0q#6p+A)3E5j^*RG^QZZl@YV6(oG>!`aM|gcG7Rj5|(AFsbpgvz^^3UF($SOS#lqs17Wi ze|S;ovT_D1FlJ2a9Zxr_W=wMe<=>`i^7;l3Pr_k0C2>c8McwgNc8Ike3S)1^T`My- z(JR)PiZX59yKaE2Yd;|1R_Vyo=}aztCc*b*dG^+-0GQa!z*)DtZs>6@T9vRY4LF++ z59^XrSgM9w$=gFWZLbnK#}^^#Y;Ne;z z@H*d>)+gY2&h3m*tOak?jf3qmN#SUVa$<5<9CcKjBcc>0E3HtRtlC&o0-Qn5TJlYW z&!3)r(Kx~5=>g>|!S+6zK$pG*oyARiy8sbCI8MB5$Iwj}b7H%kSJX;l9Kvzsz+x!GnY)i1?~bjIF2I45`gCoZ6wL2S$5hV-hjv^29__euN6_*e&2z&pekuU!~^4{3LKLVu#X-(Jj80 z2I4R{C*sa6n<$-Yi-<{0-)G$E!DvnpL;sx|0GX3{VTi@q)$>7`@6_f9;e{2I9HW)v z`efNKBFT@%9a_sT_XK(P#_ym~Q@q$&AqNgs%X4!Mz<7!tecS|9mbuRdG2J@46n2X` zNzQ(>miNiCbghxPtSD9X7tB1w%}NS%m)CDkG;&8ObSo@|%Q!OLw+QJ@J6 zxF`Q6(unEHCe3f2grtIra~Va0_#TvS%;V`C{dp73of(B)EYthJsD=0TRiTq6_lZuQnhlFIHvtg5Vth3A)w zdJZ}tySHh08p?b_PmcFm^CX$Z9X%J#%qUPrT@dCfD{7rY433-1fk=Z0?#{ijTiwWt zh%K2b-xivY_)PDVSb`Jckw39A=m2)Z1;_g;D10)+Bnll@hU2Z%uR(s}R%fai*Pdfz zbw)n1rCZK?A`tyEf3~v?AsI~J0RN^mXX`#2VC8Oa$qvmhR5+n}KauNneTJnkRR_GD zt$Yt25YM9wVg3SQMJ?o$8vHwN@Xqo%>(g~>ePMnz8JqI%&g6c-@nkGSY>GzbUa?s_ zW04yHM-e{X63keZeU_Hhf@w!e89pUlnz}JuLI1?Ec8)p zL*PFD6epY7`xY)7mP`5K0BfI`P=Rgpoyf9v1c z@$x2JRh0!~lOV8BaYB7L>e!7{6=+gLs|tIY_XKnFsz`i9qgM>zDui*ZTb@ z;*-c~rum^E4|H*wfC>=T#C546&7LzbV*SSucwU;W*W(E(i5U~3T~QuoRP&J@;DW7a zY@aG(Rbrh4@@WP#ri*qBx|Ms_f#|z6^2Sow7;XODC0mzaW!K0Q>DMZ&>=v)mH>V=0 zna8g~?ALZ~@Qv-Zhnp_9AzE1sqQX0eeXVi$YnD%gRatAcgkEsjnswlIB=A=hi5^M{ z!A88>R@+iu{iMz16@G&ydkAjl$gssr>ItRmCBG~!D?2ZWySDv!nW?rS3wsAs(|NPk zXm+@>*%2+5gsUjC_t{3kr3uS9-RTozb~MaaJ8CEBGuk;i;b_`RA>fjHDaRS8ix<9eE( zee$%s9StzVW5xnmB8&D0cDBuV-WS<5UgZU3JG%?gEz5XZ@zFM34v)dZC|UPP;T~iR zKd$V0#=NO~I2=bFO_&6ikjS_buTP8Z)mO%bkY*2rLV6>9kJzu9aL%Te&!1Ap-k4}G zNh=Zg0zWzK)MAWYTvFv_T2!$Lsso+{o{Hv*dG?wSm8;Y%