├── .gitignore ├── .travis.yml ├── ATTRIBUTION.md ├── LICENSE.txt ├── README.md ├── app ├── build.gradle └── src │ ├── androidTestInternal │ └── java │ │ └── com │ │ └── jakewharton │ │ └── u2020 │ │ ├── U2020TestRunner.java │ │ └── ui │ │ └── DummyTest.java │ ├── internal │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── jakewharton │ │ │ └── u2020 │ │ │ └── ui │ │ │ ├── bugreport │ │ │ ├── BugReportDialog.java │ │ │ ├── BugReportLens.java │ │ │ └── BugReportView.java │ │ │ └── misc │ │ │ └── EmptyTextWatcher.java │ └── res │ │ ├── layout │ │ └── bugreport_view.xml │ │ └── values │ │ └── colors.xml │ ├── internalDebug │ ├── AndroidManifest.xml │ ├── assets │ │ └── avatar │ │ │ ├── one.png │ │ │ ├── three.png │ │ │ └── two.png │ ├── java │ │ └── com │ │ │ └── jakewharton │ │ │ └── u2020 │ │ │ ├── DebugActionsModule.java │ │ │ ├── DebugU2020Module.java │ │ │ ├── IsInstrumentationTest.java │ │ │ ├── Modules.java │ │ │ ├── data │ │ │ ├── AnimationSpeed.java │ │ │ ├── ApiEndpoint.java │ │ │ ├── ApiEndpoints.java │ │ │ ├── CaptureIntents.java │ │ │ ├── DebugDataModule.java │ │ │ ├── IsMockMode.java │ │ │ ├── MockRequestHandler.java │ │ │ ├── NetworkDelay.java │ │ │ ├── NetworkErrorCode.java │ │ │ ├── NetworkErrorPercent.java │ │ │ ├── NetworkFailurePercent.java │ │ │ ├── NetworkVariancePercent.java │ │ │ ├── PicassoDebugging.java │ │ │ ├── PixelGridEnabled.java │ │ │ ├── PixelRatioEnabled.java │ │ │ ├── ScalpelEnabled.java │ │ │ ├── ScalpelWireframeEnabled.java │ │ │ ├── SeenDebugDrawer.java │ │ │ ├── api │ │ │ │ ├── DebugApiModule.java │ │ │ │ └── SharedPreferencesMockResponseSupplier.java │ │ │ └── prefs │ │ │ │ └── InetSocketAddressPreferenceAdapter.java │ │ │ ├── ui │ │ │ ├── DebugActivity.java │ │ │ ├── DebugUiModule.java │ │ │ ├── debug │ │ │ │ ├── AnimationSpeedAdapter.java │ │ │ │ ├── ContextualDebugActions.java │ │ │ │ ├── DebugDrawerLayout.java │ │ │ │ ├── DebugView.java │ │ │ │ ├── DebugViewContainer.java │ │ │ │ ├── DrawerLayoutCompatApi21.java │ │ │ │ ├── DrawerLayoutImpl.java │ │ │ │ ├── HierarchyTreeChangeListener.java │ │ │ │ ├── NetworkDelayAdapter.java │ │ │ │ ├── NetworkPercentageAdapter.java │ │ │ │ ├── NetworkVarianceAdapter.java │ │ │ │ ├── NonConsumingScrimInsetsFrameLayout.java │ │ │ │ └── ProxyAdapter.java │ │ │ ├── logs │ │ │ │ ├── LogAdapter.java │ │ │ │ └── LogsDialog.java │ │ │ └── trending │ │ │ │ ├── ScrollBottomTrendingDebugAction.java │ │ │ │ └── ScrollTopTrendingDebugAction.java │ │ │ └── util │ │ │ ├── EmptyActivityLifecycleCallbacks.java │ │ │ └── Keyboards.java │ └── res │ │ ├── drawable-hdpi │ │ ├── debug_drawer_header_bg.9.png │ │ ├── debug_drawer_icon_edit.png │ │ ├── debug_drawer_shadow.9.png │ │ └── dev_app_icon.png │ │ ├── drawable-mdpi │ │ ├── debug_drawer_header_bg.9.png │ │ ├── debug_drawer_icon_edit.png │ │ ├── debug_drawer_shadow.9.png │ │ └── dev_app_icon.png │ │ ├── drawable-xhdpi │ │ ├── debug_drawer_header_bg.9.png │ │ ├── debug_drawer_icon_edit.png │ │ ├── debug_drawer_shadow.9.png │ │ └── dev_app_icon.png │ │ ├── drawable-xxhdpi │ │ ├── debug_drawer_icon_edit.png │ │ ├── debug_drawer_shadow.9.png │ │ └── dev_app_icon.png │ │ ├── drawable-xxxhdpi │ │ └── dev_app_icon.png │ │ ├── layout │ │ ├── debug_activity.xml │ │ ├── debug_activity_frame.xml │ │ ├── debug_drawer_contextual_action.xml │ │ ├── debug_drawer_network_endpoint.xml │ │ ├── debug_drawer_network_proxy.xml │ │ ├── debug_logs_list_item.xml │ │ └── debug_view_content.xml │ │ └── values │ │ ├── debug_attrs.xml │ │ ├── debug_colors.xml │ │ ├── debug_strings.xml │ │ ├── debug_styles.xml │ │ └── titles.xml │ ├── internalRelease │ ├── java │ │ └── com │ │ │ └── jakewharton │ │ │ └── u2020 │ │ │ ├── InternalReleaseU2020Module.java │ │ │ ├── Modules.java │ │ │ └── ui │ │ │ ├── InternalReleaseUiModule.java │ │ │ └── TelescopeViewContainer.java │ └── res │ │ ├── drawable-xxhdpi │ │ ├── telescope_tutorial_thumbs_up.png │ │ └── telescope_tutorial_two_finger.png │ │ ├── layout │ │ ├── internal_activity_frame.xml │ │ ├── telescope_tutorial_dialog.xml │ │ └── telescope_tutorial_toast.xml │ │ └── values │ │ └── titles.xml │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── jakewharton │ │ │ └── u2020 │ │ │ ├── U2020App.java │ │ │ ├── U2020Module.java │ │ │ ├── data │ │ │ ├── DataModule.java │ │ │ ├── Funcs.java │ │ │ ├── Injector.java │ │ │ ├── InstantAdapter.java │ │ │ ├── LumberYard.java │ │ │ └── api │ │ │ │ ├── ApiModule.java │ │ │ │ ├── Results.java │ │ │ │ ├── oauth │ │ │ │ ├── AccessToken.java │ │ │ │ ├── OauthInterceptor.java │ │ │ │ ├── OauthManager.java │ │ │ │ └── OauthService.java │ │ │ │ └── transforms │ │ │ │ └── SearchResultToRepositoryList.java │ │ │ ├── ui │ │ │ ├── MainActivity.java │ │ │ ├── MainActivityModule.java │ │ │ ├── UiModule.java │ │ │ ├── ViewContainer.java │ │ │ ├── misc │ │ │ │ ├── AspectRatioImageView.java │ │ │ │ ├── BetterViewAnimator.java │ │ │ │ ├── BindableAdapter.java │ │ │ │ ├── DividerItemDecoration.java │ │ │ │ ├── EnumAdapter.java │ │ │ │ └── Truss.java │ │ │ ├── transform │ │ │ │ └── CircleStrokeTransformation.java │ │ │ └── trending │ │ │ │ ├── TrendingAdapter.java │ │ │ │ ├── TrendingItemView.java │ │ │ │ ├── TrendingTimespan.java │ │ │ │ ├── TrendingTimespanAdapter.java │ │ │ │ └── TrendingView.java │ │ │ └── util │ │ │ ├── Intents.java │ │ │ └── Strings.java │ └── res │ │ ├── drawable-hdpi │ │ ├── app_icon.png │ │ ├── avatar.png │ │ ├── drawer_shadow.9.png │ │ ├── fork.png │ │ ├── menu_icon.png │ │ ├── nav_search.png │ │ ├── nav_trending.png │ │ └── star.png │ │ ├── drawable-mdpi │ │ ├── app_icon.png │ │ ├── avatar.png │ │ ├── drawer_shadow.9.png │ │ ├── fork.png │ │ ├── menu_icon.png │ │ ├── nav_search.png │ │ ├── nav_trending.png │ │ └── star.png │ │ ├── drawable-v21 │ │ └── selectable_item_background.xml │ │ ├── drawable-xhdpi │ │ ├── app_icon.png │ │ ├── avatar.png │ │ ├── drawer_shadow.9.png │ │ ├── ellipsis_20.png │ │ ├── ellipsis_21.png │ │ ├── ellipsis_22.png │ │ ├── ellipsis_23.png │ │ ├── ellipsis_24.png │ │ ├── ellipsis_25.png │ │ ├── ellipsis_26.png │ │ ├── ellipsis_27.png │ │ ├── ellipsis_28.png │ │ ├── ellipsis_29.png │ │ ├── ellipsis_30.png │ │ ├── ellipsis_31.png │ │ ├── ellipsis_32.png │ │ ├── ellipsis_33.png │ │ ├── ellipsis_34.png │ │ ├── ellipsis_35.png │ │ ├── ellipsis_36.png │ │ ├── ellipsis_37.png │ │ ├── ellipsis_38.png │ │ ├── ellipsis_39.png │ │ ├── fork.png │ │ ├── menu_icon.png │ │ ├── nav_search.png │ │ ├── nav_trending.png │ │ ├── progress_indeterminate_40.png │ │ └── star.png │ │ ├── drawable-xxhdpi │ │ ├── app_icon.png │ │ ├── avatar.png │ │ ├── drawer_header_img.png │ │ ├── drawer_shadow.9.png │ │ ├── empty.png │ │ ├── error.png │ │ ├── fork.png │ │ ├── loading.png │ │ ├── menu_icon.png │ │ ├── nav_search.png │ │ ├── nav_trending.png │ │ └── star.png │ │ ├── drawable-xxxhdpi │ │ ├── app_icon.png │ │ ├── avatar.png │ │ ├── fork.png │ │ ├── menu_icon.png │ │ ├── nav_search.png │ │ ├── nav_trending.png │ │ └── star.png │ │ ├── drawable │ │ ├── dancing_ellipsis.xml │ │ ├── drawer_header.xml │ │ ├── popup_background.xml │ │ ├── progress_indeterminate.xml │ │ └── selectable_item_background.xml │ │ ├── layout │ │ ├── main_activity.xml │ │ ├── main_drawer_header.xml │ │ ├── trending_timespan_view.xml │ │ ├── trending_view.xml │ │ └── trending_view_repository.xml │ │ ├── menu │ │ └── main_drawer.xml │ │ └── values │ │ ├── attrs.xml │ │ ├── colors.xml │ │ ├── dimens.xml │ │ ├── strings.xml │ │ └── styles.xml │ ├── production │ └── java │ │ └── com │ │ └── jakewharton │ │ └── u2020 │ │ └── Modules.java │ ├── productionDebug │ └── res │ │ └── values │ │ └── titles.xml │ ├── productionRelease │ └── res │ │ └── values │ │ └── titles.xml │ └── test │ └── java │ └── com │ └── jakewharton │ └── u2020 │ └── data │ └── InstantAdapterTest.java ├── art ├── app-icon.afdesign ├── app_icon_web.png └── phone_faces.psd ├── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── intent-factory ├── build.gradle └── src │ ├── debug │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── jakewharton │ │ │ └── u2020 │ │ │ └── intentfactory │ │ │ ├── CapturedIntentActivity.java │ │ │ └── CapturingIntentFactory.java │ └── res │ │ ├── drawable │ │ └── captured_intent_launch.xml │ │ ├── layout │ │ └── captured_intent.xml │ │ └── values │ │ └── styles.xml │ └── main │ ├── AndroidManifest.xml │ └── java │ └── com │ └── jakewharton │ └── u2020 │ └── intentfactory │ └── IntentFactory.java ├── lint.xml ├── scripts └── strip-image-metadata.sh ├── service ├── github-mock │ ├── build.gradle │ └── src │ │ └── main │ │ └── java │ │ └── com │ │ └── jakewharton │ │ └── u2020 │ │ └── data │ │ └── api │ │ ├── MockGithubService.java │ │ ├── MockRepositories.java │ │ ├── MockRepositoriesResponse.java │ │ ├── MockResponseSupplier.java │ │ ├── MockUsers.java │ │ └── SortUtil.java └── github │ ├── build.gradle │ └── src │ └── main │ └── java │ └── com │ └── jakewharton │ └── u2020 │ └── data │ └── api │ ├── GithubService.java │ ├── Order.java │ ├── SearchQuery.java │ ├── Sort.java │ └── model │ ├── RepositoriesResponse.java │ ├── Repository.java │ └── User.java ├── settings.gradle ├── u2020.gif └── u2020.keystore /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | reports 3 | local.properties 4 | .idea 5 | *.iml 6 | build 7 | .DS_Store 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: android 2 | 3 | jdk: 4 | - oraclejdk8 5 | 6 | before_install: 7 | # Install SDK license so Android Gradle plugin can install deps. 8 | - mkdir "$ANDROID_HOME/licenses" || true 9 | - echo "d56f5187479451eabf01fb78af6dfcb131a6481e" > "$ANDROID_HOME/licenses/android-sdk-license" 10 | # Install the rest of tools (e.g., avdmanager). 11 | - sdkmanager tools 12 | # Install the system image. 13 | - sdkmanager "system-images;android-19;default;armeabi-v7a" 14 | # Create and start emulator for the script. Meant to race the install task. 15 | - echo no | avdmanager create avd --force -n test -k "system-images;android-19;default;armeabi-v7a" 16 | - $ANDROID_HOME/emulator/emulator -avd test -no-audio -no-window & 17 | 18 | install: ./gradlew clean assemble --stacktrace 19 | 20 | before_script: 21 | - android-wait-for-emulator 22 | - adb shell settings put global window_animation_scale 0 & 23 | - adb shell settings put global transition_animation_scale 0 & 24 | - adb shell settings put global animator_duration_scale 0 & 25 | - adb shell input keyevent 82 & 26 | 27 | script: ./gradlew check connectedCheck --stacktrace 28 | 29 | env: 30 | global: 31 | - ADB_INSTALL_TIMEOUT=8 32 | 33 | notifications: 34 | email: false 35 | 36 | sudo: false 37 | 38 | cache: 39 | directories: 40 | - $HOME/.gradle 41 | -------------------------------------------------------------------------------- /ATTRIBUTION.md: -------------------------------------------------------------------------------- 1 | error.png 2 | CC BY 3.0 3 | http://thenounproject.com/term/dead/78575/ 4 | 5 | loading.png 6 | CC BY 3.0 7 | http://thenounproject.com/term/smile/71417/ 8 | 9 | error.png 10 | CC BY 3.0 11 | Modified from http://thenounproject.com/term/smile/71417/ 12 | 13 | telescope_tutorial_thumbs_up.png 14 | CC BY 3.0 15 | https://thenounproject.com/term/thumbs-up/40917/ 16 | 17 | telescope_tutorial_two_finger.png 18 | CC BY 3.0 19 | https://thenounproject.com/term/tap-and-hold/13944/ 20 | -------------------------------------------------------------------------------- /app/src/androidTestInternal/java/com/jakewharton/u2020/U2020TestRunner.java: -------------------------------------------------------------------------------- 1 | package com.jakewharton.u2020; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.app.KeyguardManager; 5 | import android.content.Context; 6 | import android.os.PowerManager; 7 | import android.support.test.runner.AndroidJUnitRunner; 8 | 9 | import static android.content.Context.KEYGUARD_SERVICE; 10 | import static android.content.Context.POWER_SERVICE; 11 | import static android.os.PowerManager.ACQUIRE_CAUSES_WAKEUP; 12 | import static android.os.PowerManager.FULL_WAKE_LOCK; 13 | import static android.os.PowerManager.ON_AFTER_RELEASE; 14 | 15 | public final class U2020TestRunner extends AndroidJUnitRunner { 16 | private PowerManager.WakeLock wakeLock; 17 | 18 | @SuppressLint("MissingPermission") 19 | @Override public void onStart() { 20 | // Inform the app we are an instrumentation test before the object graph is initialized. 21 | DebugU2020Module.instrumentationTest = true; 22 | 23 | Context app = getTargetContext().getApplicationContext(); 24 | 25 | String name = U2020TestRunner.class.getSimpleName(); 26 | // Unlock the device so that the tests can input keystrokes. 27 | KeyguardManager keyguard = (KeyguardManager) app.getSystemService(KEYGUARD_SERVICE); 28 | keyguard.newKeyguardLock(name).disableKeyguard(); 29 | // Wake up the screen. 30 | PowerManager power = (PowerManager) app.getSystemService(POWER_SERVICE); 31 | wakeLock = power.newWakeLock(FULL_WAKE_LOCK | ACQUIRE_CAUSES_WAKEUP | ON_AFTER_RELEASE, name); 32 | wakeLock.acquire(); 33 | 34 | super.onStart(); 35 | } 36 | 37 | @Override public void onDestroy() { 38 | super.onDestroy(); 39 | 40 | wakeLock.release(); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /app/src/androidTestInternal/java/com/jakewharton/u2020/ui/DummyTest.java: -------------------------------------------------------------------------------- 1 | package com.jakewharton.u2020.ui; 2 | 3 | import android.support.test.rule.ActivityTestRule; 4 | import android.support.test.runner.AndroidJUnit4; 5 | import org.junit.Rule; 6 | import org.junit.Test; 7 | import org.junit.runner.RunWith; 8 | import com.squareup.spoon.Spoon; 9 | 10 | import static android.os.SystemClock.sleep; 11 | import static java.util.concurrent.TimeUnit.SECONDS; 12 | import static org.junit.Assert.assertTrue; 13 | 14 | @RunWith(AndroidJUnit4.class) 15 | public final class DummyTest { 16 | @Rule public final ActivityTestRule main = 17 | new ActivityTestRule<>(MainActivity.class); 18 | 19 | @Test public void noneOfTheThings() { 20 | Spoon.screenshot(main.getActivity(), "initial_state"); 21 | sleep(SECONDS.toMillis(5)); // Long enough to see some data from mock mode. 22 | assertTrue(true); 23 | Spoon.screenshot(main.getActivity(), "displayed_data"); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/src/internal/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/internal/java/com/jakewharton/u2020/ui/bugreport/BugReportDialog.java: -------------------------------------------------------------------------------- 1 | package com.jakewharton.u2020.ui.bugreport; 2 | 3 | 4 | import android.app.Dialog; 5 | import android.content.Context; 6 | import android.support.v7.app.AlertDialog; 7 | import android.view.LayoutInflater; 8 | import com.jakewharton.u2020.R; 9 | 10 | import static com.jakewharton.u2020.ui.bugreport.BugReportView.Report; 11 | import static com.jakewharton.u2020.ui.bugreport.BugReportView.ReportDetailsListener; 12 | 13 | public final class BugReportDialog extends AlertDialog implements ReportDetailsListener { 14 | public interface ReportListener { 15 | void onBugReportSubmit(Report report); 16 | } 17 | 18 | private ReportListener listener; 19 | 20 | public BugReportDialog(Context context) { 21 | super(context); 22 | 23 | final BugReportView view = 24 | (BugReportView) LayoutInflater.from(context).inflate(R.layout.bugreport_view, null); 25 | view.setBugReportListener(this); 26 | 27 | setTitle("Report a bug"); 28 | setView(view); 29 | setButton(Dialog.BUTTON_NEGATIVE, "Cancel", (OnClickListener) null); 30 | setButton(Dialog.BUTTON_POSITIVE, "Submit", (dialog, which) -> { 31 | if (listener != null) { 32 | listener.onBugReportSubmit(view.getReport()); 33 | } 34 | }); 35 | } 36 | 37 | public void setReportListener(ReportListener listener) { 38 | this.listener = listener; 39 | } 40 | 41 | @Override protected void onStart() { 42 | getButton(Dialog.BUTTON_POSITIVE).setEnabled(false); 43 | } 44 | 45 | @Override public void onStateChanged(boolean valid) { 46 | getButton(Dialog.BUTTON_POSITIVE).setEnabled(valid); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /app/src/internal/java/com/jakewharton/u2020/ui/bugreport/BugReportView.java: -------------------------------------------------------------------------------- 1 | package com.jakewharton.u2020.ui.bugreport; 2 | 3 | import android.content.Context; 4 | import android.text.Editable; 5 | import android.util.AttributeSet; 6 | import android.widget.CheckBox; 7 | import android.widget.EditText; 8 | import android.widget.LinearLayout; 9 | import butterknife.BindView; 10 | import butterknife.ButterKnife; 11 | import com.jakewharton.u2020.R; 12 | import com.jakewharton.u2020.ui.misc.EmptyTextWatcher; 13 | import com.jakewharton.u2020.util.Strings; 14 | 15 | public final class BugReportView extends LinearLayout { 16 | @BindView(R.id.title) EditText titleView; 17 | @BindView(R.id.description) EditText descriptionView; 18 | @BindView(R.id.screenshot) CheckBox screenshotView; 19 | @BindView(R.id.logs) CheckBox logsView; 20 | 21 | public interface ReportDetailsListener { 22 | void onStateChanged(boolean valid); 23 | } 24 | 25 | private ReportDetailsListener listener; 26 | 27 | public BugReportView(Context context, AttributeSet attrs) { 28 | super(context, attrs); 29 | } 30 | 31 | @Override protected void onFinishInflate() { 32 | super.onFinishInflate(); 33 | ButterKnife.bind(this); 34 | 35 | titleView.setOnFocusChangeListener((v, hasFocus) -> { 36 | if (!hasFocus) { 37 | titleView.setError(Strings.isBlank(titleView.getText()) ? "Cannot be empty." : null); 38 | } 39 | }); 40 | titleView.addTextChangedListener(new EmptyTextWatcher() { 41 | @Override public void afterTextChanged(Editable s) { 42 | if (listener != null) { 43 | listener.onStateChanged(!Strings.isBlank(s)); 44 | } 45 | } 46 | }); 47 | 48 | screenshotView.setChecked(true); 49 | logsView.setChecked(true); 50 | } 51 | 52 | public void setBugReportListener(ReportDetailsListener listener) { 53 | this.listener = listener; 54 | } 55 | 56 | public Report getReport() { 57 | return new Report(String.valueOf(titleView.getText()), 58 | String.valueOf(descriptionView.getText()), screenshotView.isChecked(), 59 | logsView.isChecked()); 60 | } 61 | 62 | public static final class Report { 63 | public final String title; 64 | public final String description; 65 | public final boolean includeScreenshot; 66 | public final boolean includeLogs; 67 | 68 | public Report(String title, String description, boolean includeScreenshot, 69 | boolean includeLogs) { 70 | this.title = title; 71 | this.description = description; 72 | this.includeScreenshot = includeScreenshot; 73 | this.includeLogs = includeLogs; 74 | } 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /app/src/internal/java/com/jakewharton/u2020/ui/misc/EmptyTextWatcher.java: -------------------------------------------------------------------------------- 1 | package com.jakewharton.u2020.ui.misc; 2 | 3 | import android.text.Editable; 4 | import android.text.TextWatcher; 5 | 6 | public class EmptyTextWatcher implements TextWatcher { 7 | @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { 8 | } 9 | 10 | @Override public void onTextChanged(CharSequence s, int start, int before, int count) { 11 | } 12 | 13 | @Override public void afterTextChanged(Editable s) { 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /app/src/internal/res/layout/bugreport_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 20 | 28 | 38 | 48 | 49 | -------------------------------------------------------------------------------- /app/src/internal/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | #FF5722 5 | 6 | -------------------------------------------------------------------------------- /app/src/internalDebug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 23 | 24 | 25 | 26 | 27 | 28 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /app/src/internalDebug/assets/avatar/one.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeWharton/u2020/af670f964a4750707d98cda31cfa95244eb3ec07/app/src/internalDebug/assets/avatar/one.png -------------------------------------------------------------------------------- /app/src/internalDebug/assets/avatar/three.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeWharton/u2020/af670f964a4750707d98cda31cfa95244eb3ec07/app/src/internalDebug/assets/avatar/three.png -------------------------------------------------------------------------------- /app/src/internalDebug/assets/avatar/two.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeWharton/u2020/af670f964a4750707d98cda31cfa95244eb3ec07/app/src/internalDebug/assets/avatar/two.png -------------------------------------------------------------------------------- /app/src/internalDebug/java/com/jakewharton/u2020/DebugActionsModule.java: -------------------------------------------------------------------------------- 1 | package com.jakewharton.u2020; 2 | 3 | import com.jakewharton.u2020.ui.debug.ContextualDebugActions.DebugAction; 4 | import com.jakewharton.u2020.ui.trending.ScrollBottomTrendingDebugAction; 5 | import com.jakewharton.u2020.ui.trending.ScrollTopTrendingDebugAction; 6 | import dagger.Module; 7 | import dagger.Provides; 8 | import java.util.LinkedHashSet; 9 | import java.util.Set; 10 | 11 | import static dagger.Provides.Type.SET_VALUES; 12 | 13 | @Module(complete = false, library = true) public final class DebugActionsModule { 14 | @Provides(type = SET_VALUES) Set provideDebugActions( 15 | ScrollBottomTrendingDebugAction scrollBottomTrendingAction, 16 | ScrollTopTrendingDebugAction scrollTopTrendingAction) { 17 | Set actions = new LinkedHashSet<>(); 18 | actions.add(scrollTopTrendingAction); 19 | actions.add(scrollBottomTrendingAction); 20 | return actions; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /app/src/internalDebug/java/com/jakewharton/u2020/DebugU2020Module.java: -------------------------------------------------------------------------------- 1 | package com.jakewharton.u2020; 2 | 3 | import com.jakewharton.u2020.data.DebugDataModule; 4 | import com.jakewharton.u2020.ui.DebugUiModule; 5 | import dagger.Module; 6 | import dagger.Provides; 7 | import javax.inject.Singleton; 8 | 9 | @Module( 10 | addsTo = U2020Module.class, 11 | includes = { 12 | DebugUiModule.class, 13 | DebugDataModule.class, 14 | DebugActionsModule.class 15 | }, 16 | overrides = true 17 | ) 18 | public final class DebugU2020Module { 19 | // Low-tech flag to force certain debug build behaviors when running in an instrumentation test. 20 | // This value is used in the creation of singletons so it must be set before the graph is created. 21 | static boolean instrumentationTest = false; 22 | 23 | @Provides @Singleton @IsInstrumentationTest boolean provideIsInstrumentationTest() { 24 | return instrumentationTest; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/internalDebug/java/com/jakewharton/u2020/IsInstrumentationTest.java: -------------------------------------------------------------------------------- 1 | package com.jakewharton.u2020; 2 | 3 | import java.lang.annotation.Retention; 4 | import javax.inject.Qualifier; 5 | 6 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 7 | 8 | @Qualifier @Retention(RUNTIME) 9 | public @interface IsInstrumentationTest { 10 | } 11 | -------------------------------------------------------------------------------- /app/src/internalDebug/java/com/jakewharton/u2020/Modules.java: -------------------------------------------------------------------------------- 1 | package com.jakewharton.u2020; 2 | 3 | final class Modules { 4 | static Object[] list(U2020App app) { 5 | return new Object[] { 6 | new U2020Module(app), 7 | new DebugU2020Module() 8 | }; 9 | } 10 | 11 | private Modules() { 12 | // No instances. 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /app/src/internalDebug/java/com/jakewharton/u2020/data/AnimationSpeed.java: -------------------------------------------------------------------------------- 1 | package com.jakewharton.u2020.data; 2 | 3 | import java.lang.annotation.Retention; 4 | import javax.inject.Qualifier; 5 | 6 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 7 | 8 | @Qualifier @Retention(RUNTIME) 9 | public @interface AnimationSpeed { 10 | } 11 | -------------------------------------------------------------------------------- /app/src/internalDebug/java/com/jakewharton/u2020/data/ApiEndpoint.java: -------------------------------------------------------------------------------- 1 | package com.jakewharton.u2020.data; 2 | 3 | import java.lang.annotation.Retention; 4 | import javax.inject.Qualifier; 5 | 6 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 7 | 8 | @Qualifier @Retention(RUNTIME) 9 | public @interface ApiEndpoint { 10 | } 11 | -------------------------------------------------------------------------------- /app/src/internalDebug/java/com/jakewharton/u2020/data/ApiEndpoints.java: -------------------------------------------------------------------------------- 1 | package com.jakewharton.u2020.data; 2 | 3 | import com.jakewharton.u2020.data.api.ApiModule; 4 | 5 | public enum ApiEndpoints { 6 | PRODUCTION("Production", ApiModule.PRODUCTION_API_URL.toString()), 7 | // STAGING("Staging", "https://api.staging.github.com/"), 8 | MOCK_MODE("Mock Mode", "http://localhost/mock/"), 9 | CUSTOM("Custom", null); 10 | 11 | public final String name; 12 | public final String url; 13 | 14 | ApiEndpoints(String name, String url) { 15 | this.name = name; 16 | this.url = url; 17 | } 18 | 19 | @Override public String toString() { 20 | return name; 21 | } 22 | 23 | public static ApiEndpoints from(String endpoint) { 24 | for (ApiEndpoints value : values()) { 25 | if (value.url != null && value.url.equals(endpoint)) { 26 | return value; 27 | } 28 | } 29 | return CUSTOM; 30 | } 31 | 32 | public static boolean isMockMode(String endpoint) { 33 | return from(endpoint) == MOCK_MODE; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/src/internalDebug/java/com/jakewharton/u2020/data/CaptureIntents.java: -------------------------------------------------------------------------------- 1 | package com.jakewharton.u2020.data; 2 | 3 | import java.lang.annotation.Retention; 4 | import javax.inject.Qualifier; 5 | 6 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 7 | 8 | @Qualifier @Retention(RUNTIME) 9 | public @interface CaptureIntents { 10 | } 11 | -------------------------------------------------------------------------------- /app/src/internalDebug/java/com/jakewharton/u2020/data/IsMockMode.java: -------------------------------------------------------------------------------- 1 | package com.jakewharton.u2020.data; 2 | 3 | import java.lang.annotation.Retention; 4 | import javax.inject.Qualifier; 5 | 6 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 7 | 8 | @Qualifier @Retention(RUNTIME) 9 | public @interface IsMockMode { 10 | } 11 | -------------------------------------------------------------------------------- /app/src/internalDebug/java/com/jakewharton/u2020/data/MockRequestHandler.java: -------------------------------------------------------------------------------- 1 | package com.jakewharton.u2020.data; 2 | 3 | import android.content.res.AssetFileDescriptor; 4 | import android.content.res.AssetManager; 5 | import android.graphics.Bitmap; 6 | import android.graphics.BitmapFactory; 7 | import android.os.SystemClock; 8 | import android.util.LruCache; 9 | import com.squareup.picasso.Downloader; 10 | import com.squareup.picasso.NetworkPolicy; 11 | import com.squareup.picasso.Picasso; 12 | import com.squareup.picasso.Request; 13 | import com.squareup.picasso.RequestHandler; 14 | import java.io.IOException; 15 | import retrofit2.mock.NetworkBehavior; 16 | 17 | import static java.util.concurrent.TimeUnit.MILLISECONDS; 18 | 19 | /** 20 | * A Picasso {@link Downloader} which loads images from assets but attempts to emulate the 21 | * subtleties of a real HTTP client and its disk cache. 22 | *

23 | * Images must be in the form {@code mock:///path/to/asset.png}. 24 | */ 25 | public final class MockRequestHandler extends RequestHandler { 26 | private final NetworkBehavior behavior; 27 | private final AssetManager assetManager; 28 | 29 | /** Emulate the disk cache by storing the URLs in an LRU using its size as the value. */ 30 | private final LruCache emulatedDiskCache = 31 | new LruCache(((int) Math.min(DataModule.DISK_CACHE_SIZE, Integer.MAX_VALUE))) { 32 | @Override protected int sizeOf(String key, Long value) { 33 | return (int) Math.min(value.longValue(), Integer.MAX_VALUE); 34 | } 35 | }; 36 | 37 | public MockRequestHandler(NetworkBehavior behavior, AssetManager assetManager) { 38 | this.behavior = behavior; 39 | this.assetManager = assetManager; 40 | } 41 | 42 | @Override public boolean canHandleRequest(Request data) { 43 | return "mock".equals(data.uri.getScheme()); 44 | } 45 | 46 | @Override public Result load(Request request, int networkPolicy) throws IOException { 47 | String imagePath = request.uri.getPath().substring(1); // Grab only the path sans leading slash. 48 | 49 | // Check the disk cache for the image. A non-null return value indicates a hit. 50 | boolean cacheHit = emulatedDiskCache.get(imagePath) != null; 51 | 52 | // If there's a hit, grab the image stream and return it. 53 | if (cacheHit) { 54 | return new Result(loadBitmap(imagePath), Picasso.LoadedFrom.DISK); 55 | } 56 | 57 | // If we are not allowed to hit the network and the cache missed return a big fat nothing. 58 | if (NetworkPolicy.isOfflineOnly(networkPolicy)) { 59 | return null; 60 | } 61 | 62 | // If we got this far there was a cache miss and hitting the network is required. See if we need 63 | // to fake an network error. 64 | if (behavior.calculateIsFailure()) { 65 | SystemClock.sleep(behavior.calculateDelay(MILLISECONDS)); 66 | throw new IOException("Fake network error!"); 67 | } 68 | 69 | // We aren't throwing a network error so fake a round trip delay. 70 | SystemClock.sleep(behavior.calculateDelay(MILLISECONDS)); 71 | 72 | // Since we cache missed put it in the LRU. 73 | AssetFileDescriptor fileDescriptor = assetManager.openFd(imagePath); 74 | long size = fileDescriptor.getLength(); 75 | fileDescriptor.close(); 76 | 77 | emulatedDiskCache.put(imagePath, size); 78 | 79 | // Grab the image stream and return it. 80 | return new Result(loadBitmap(imagePath), Picasso.LoadedFrom.NETWORK); 81 | } 82 | 83 | Bitmap loadBitmap(String imagePath) throws IOException { 84 | return BitmapFactory.decodeStream(assetManager.open(imagePath)); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /app/src/internalDebug/java/com/jakewharton/u2020/data/NetworkDelay.java: -------------------------------------------------------------------------------- 1 | package com.jakewharton.u2020.data; 2 | 3 | import java.lang.annotation.Retention; 4 | import javax.inject.Qualifier; 5 | 6 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 7 | 8 | @Qualifier @Retention(RUNTIME) 9 | public @interface NetworkDelay { 10 | } 11 | -------------------------------------------------------------------------------- /app/src/internalDebug/java/com/jakewharton/u2020/data/NetworkErrorCode.java: -------------------------------------------------------------------------------- 1 | package com.jakewharton.u2020.data; 2 | 3 | public enum NetworkErrorCode { 4 | HTTP_403(403), 5 | HTTP_404(404), 6 | HTTP_500(500), 7 | HTTP_501(501), 8 | HTTP_503(503), 9 | HTTP_504(504), 10 | ; 11 | 12 | public final int code; 13 | 14 | NetworkErrorCode(int code) { 15 | this.code = code; 16 | } 17 | 18 | @Override public String toString() { 19 | return "HTTP " + code; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/src/internalDebug/java/com/jakewharton/u2020/data/NetworkErrorPercent.java: -------------------------------------------------------------------------------- 1 | package com.jakewharton.u2020.data; 2 | 3 | import java.lang.annotation.Retention; 4 | import javax.inject.Qualifier; 5 | 6 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 7 | 8 | @Qualifier @Retention(RUNTIME) 9 | public @interface NetworkErrorPercent { 10 | } 11 | -------------------------------------------------------------------------------- /app/src/internalDebug/java/com/jakewharton/u2020/data/NetworkFailurePercent.java: -------------------------------------------------------------------------------- 1 | package com.jakewharton.u2020.data; 2 | 3 | import java.lang.annotation.Retention; 4 | import javax.inject.Qualifier; 5 | 6 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 7 | 8 | @Qualifier @Retention(RUNTIME) 9 | public @interface NetworkFailurePercent { 10 | } 11 | -------------------------------------------------------------------------------- /app/src/internalDebug/java/com/jakewharton/u2020/data/NetworkVariancePercent.java: -------------------------------------------------------------------------------- 1 | package com.jakewharton.u2020.data; 2 | 3 | import java.lang.annotation.Retention; 4 | import javax.inject.Qualifier; 5 | 6 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 7 | 8 | @Qualifier @Retention(RUNTIME) 9 | public @interface NetworkVariancePercent { 10 | } 11 | -------------------------------------------------------------------------------- /app/src/internalDebug/java/com/jakewharton/u2020/data/PicassoDebugging.java: -------------------------------------------------------------------------------- 1 | package com.jakewharton.u2020.data; 2 | 3 | import java.lang.annotation.Retention; 4 | import javax.inject.Qualifier; 5 | 6 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 7 | 8 | @Qualifier @Retention(RUNTIME) 9 | public @interface PicassoDebugging { 10 | } 11 | -------------------------------------------------------------------------------- /app/src/internalDebug/java/com/jakewharton/u2020/data/PixelGridEnabled.java: -------------------------------------------------------------------------------- 1 | package com.jakewharton.u2020.data; 2 | 3 | import java.lang.annotation.Retention; 4 | import javax.inject.Qualifier; 5 | 6 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 7 | 8 | @Qualifier @Retention(RUNTIME) 9 | public @interface PixelGridEnabled { 10 | } 11 | -------------------------------------------------------------------------------- /app/src/internalDebug/java/com/jakewharton/u2020/data/PixelRatioEnabled.java: -------------------------------------------------------------------------------- 1 | package com.jakewharton.u2020.data; 2 | 3 | import java.lang.annotation.Retention; 4 | import javax.inject.Qualifier; 5 | 6 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 7 | 8 | @Qualifier @Retention(RUNTIME) 9 | public @interface PixelRatioEnabled { 10 | } 11 | -------------------------------------------------------------------------------- /app/src/internalDebug/java/com/jakewharton/u2020/data/ScalpelEnabled.java: -------------------------------------------------------------------------------- 1 | package com.jakewharton.u2020.data; 2 | 3 | import java.lang.annotation.Retention; 4 | import javax.inject.Qualifier; 5 | 6 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 7 | 8 | @Qualifier @Retention(RUNTIME) 9 | public @interface ScalpelEnabled { 10 | } 11 | -------------------------------------------------------------------------------- /app/src/internalDebug/java/com/jakewharton/u2020/data/ScalpelWireframeEnabled.java: -------------------------------------------------------------------------------- 1 | package com.jakewharton.u2020.data; 2 | 3 | import java.lang.annotation.Retention; 4 | import javax.inject.Qualifier; 5 | 6 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 7 | 8 | @Qualifier @Retention(RUNTIME) 9 | public @interface ScalpelWireframeEnabled { 10 | } 11 | -------------------------------------------------------------------------------- /app/src/internalDebug/java/com/jakewharton/u2020/data/SeenDebugDrawer.java: -------------------------------------------------------------------------------- 1 | package com.jakewharton.u2020.data; 2 | 3 | import java.lang.annotation.Retention; 4 | import javax.inject.Qualifier; 5 | 6 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 7 | 8 | @Qualifier @Retention(RUNTIME) 9 | public @interface SeenDebugDrawer { 10 | } 11 | -------------------------------------------------------------------------------- /app/src/internalDebug/java/com/jakewharton/u2020/data/api/DebugApiModule.java: -------------------------------------------------------------------------------- 1 | package com.jakewharton.u2020.data.api; 2 | 3 | import android.content.SharedPreferences; 4 | import com.f2prateek.rx.preferences.Preference; 5 | import com.jakewharton.u2020.data.ApiEndpoint; 6 | import com.jakewharton.u2020.data.IsMockMode; 7 | import com.jakewharton.u2020.data.NetworkDelay; 8 | import com.jakewharton.u2020.data.NetworkErrorCode; 9 | import com.jakewharton.u2020.data.NetworkErrorPercent; 10 | import com.jakewharton.u2020.data.NetworkFailurePercent; 11 | import com.jakewharton.u2020.data.NetworkVariancePercent; 12 | import com.jakewharton.u2020.data.api.oauth.OauthInterceptor; 13 | import dagger.Module; 14 | import dagger.Provides; 15 | import javax.inject.Named; 16 | import javax.inject.Singleton; 17 | import okhttp3.HttpUrl; 18 | import okhttp3.OkHttpClient; 19 | import okhttp3.ResponseBody; 20 | import okhttp3.logging.HttpLoggingInterceptor; 21 | import retrofit2.Response; 22 | import retrofit2.Retrofit; 23 | import retrofit2.mock.MockRetrofit; 24 | import retrofit2.mock.NetworkBehavior; 25 | import timber.log.Timber; 26 | 27 | import static java.util.concurrent.TimeUnit.MILLISECONDS; 28 | 29 | @Module( 30 | complete = false, 31 | library = true, 32 | overrides = true 33 | ) 34 | public final class DebugApiModule { 35 | @Provides @Singleton HttpUrl provideHttpUrl(@ApiEndpoint Preference apiEndpoint) { 36 | return HttpUrl.parse(apiEndpoint.get()); 37 | } 38 | 39 | @Provides @Singleton HttpLoggingInterceptor provideLoggingInterceptor() { 40 | HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(message -> Timber.tag("OkHttp").v(message)); 41 | loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.HEADERS); 42 | return loggingInterceptor; 43 | } 44 | 45 | @Provides @Singleton @Named("Api") OkHttpClient provideApiClient(OkHttpClient client, 46 | OauthInterceptor oauthInterceptor, HttpLoggingInterceptor loggingInterceptor) { 47 | return ApiModule.createApiClient(client, oauthInterceptor) 48 | .addInterceptor(loggingInterceptor) 49 | .build(); 50 | } 51 | 52 | @Provides @Singleton NetworkBehavior provideBehavior(@NetworkDelay Preference networkDelay, 53 | @NetworkVariancePercent Preference networkVariancePercent, 54 | @NetworkFailurePercent Preference networkFailurePercent, 55 | @NetworkErrorPercent Preference networkErrorPercent, 56 | Preference networkErrorCode) { 57 | NetworkBehavior behavior = NetworkBehavior.create(); 58 | behavior.setDelay(networkDelay.get(), MILLISECONDS); 59 | behavior.setVariancePercent(networkVariancePercent.get()); 60 | behavior.setFailurePercent(networkFailurePercent.get()); 61 | behavior.setErrorPercent(networkErrorPercent.get()); 62 | behavior.setErrorFactory( 63 | () -> Response.error(networkErrorCode.get().code, ResponseBody.create(null, new byte[0]))); 64 | return behavior; 65 | } 66 | 67 | @Provides @Singleton MockRetrofit provideMockRetrofit(Retrofit retrofit, 68 | NetworkBehavior behavior) { 69 | return new MockRetrofit.Builder(retrofit) 70 | .networkBehavior(behavior) 71 | .build(); 72 | } 73 | 74 | @Provides @Singleton GithubService provideGithubService(Retrofit retrofit, 75 | @IsMockMode boolean isMockMode, MockGithubService mockService) { 76 | return isMockMode ? mockService : retrofit.create(GithubService.class); 77 | } 78 | 79 | @Provides @Singleton MockResponseSupplier provideResponseSupplier(SharedPreferences preferences) { 80 | return new SharedPreferencesMockResponseSupplier(preferences); 81 | } 82 | 83 | @Provides @Singleton MockGithubService provideMockGitHubService(MockRetrofit mockRetrofit, 84 | MockResponseSupplier responseSupplier) { 85 | return new MockGithubService(mockRetrofit, responseSupplier); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /app/src/internalDebug/java/com/jakewharton/u2020/data/api/SharedPreferencesMockResponseSupplier.java: -------------------------------------------------------------------------------- 1 | package com.jakewharton.u2020.data.api; 2 | 3 | import android.annotation.SuppressLint; 4 | import android.content.SharedPreferences; 5 | 6 | final class SharedPreferencesMockResponseSupplier implements MockResponseSupplier { 7 | private final SharedPreferences preferences; 8 | 9 | SharedPreferencesMockResponseSupplier(SharedPreferences preferences) { 10 | this.preferences = preferences; 11 | } 12 | 13 | @Override public > T get(Class cls) { 14 | String value = preferences.getString(cls.getCanonicalName(), null); 15 | if (value != null) { 16 | return Enum.valueOf(cls, value); 17 | } 18 | return cls.getEnumConstants()[0]; 19 | } 20 | 21 | @SuppressLint("ApplySharedPref") // Persist to disk because we might kill the process next. 22 | @Override public void set(Enum value) { 23 | preferences.edit() 24 | .putString(value.getDeclaringClass().getCanonicalName(), value.name()) 25 | .commit(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/src/internalDebug/java/com/jakewharton/u2020/data/prefs/InetSocketAddressPreferenceAdapter.java: -------------------------------------------------------------------------------- 1 | package com.jakewharton.u2020.data.prefs; 2 | 3 | import android.content.SharedPreferences; 4 | import android.support.annotation.NonNull; 5 | import android.support.annotation.Nullable; 6 | import com.f2prateek.rx.preferences.Preference; 7 | import com.jakewharton.u2020.util.Strings; 8 | import java.net.InetSocketAddress; 9 | import java.net.Proxy; 10 | 11 | import static java.net.Proxy.Type.HTTP; 12 | 13 | public class InetSocketAddressPreferenceAdapter implements Preference.Adapter { 14 | public static final InetSocketAddressPreferenceAdapter INSTANCE = 15 | new InetSocketAddressPreferenceAdapter(); 16 | 17 | InetSocketAddressPreferenceAdapter() { 18 | } 19 | 20 | @Override public void set(@NonNull String key, @NonNull InetSocketAddress address, 21 | @NonNull SharedPreferences.Editor editor) { 22 | String host = null; 23 | if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) { 24 | host = address.getHostString(); 25 | } else { 26 | host = address.getHostName(); 27 | } 28 | int port = address.getPort(); 29 | editor.putString(key, host + ":" + port); 30 | } 31 | 32 | @Override 33 | public InetSocketAddress get(@NonNull String key, @NonNull SharedPreferences preferences) { 34 | String value = preferences.getString(key, null); 35 | assert value != null; // Not called unless value is present. 36 | String[] parts = value.split(":", 2); 37 | String host = parts[0]; 38 | int port = parts.length > 1 ? Integer.parseInt(parts[1]) : 80; 39 | return InetSocketAddress.createUnresolved(host, port); 40 | } 41 | 42 | public static @Nullable InetSocketAddress parse(@Nullable String value) { 43 | if (Strings.isBlank(value)) { 44 | return null; 45 | } 46 | String[] parts = value.split(":", 2); 47 | if (parts.length == 0) { 48 | return null; 49 | } 50 | String host = parts[0]; 51 | int port = parts.length > 1 ? Integer.parseInt(parts[1]) : 80; 52 | return InetSocketAddress.createUnresolved(host, port); 53 | } 54 | 55 | public static @Nullable Proxy createProxy(@Nullable InetSocketAddress address) { 56 | if (address == null) { 57 | return null; 58 | } 59 | return new Proxy(HTTP, address); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /app/src/internalDebug/java/com/jakewharton/u2020/ui/DebugActivity.java: -------------------------------------------------------------------------------- 1 | package com.jakewharton.u2020.ui; 2 | 3 | import android.app.Activity; 4 | import android.os.Bundle; 5 | import android.support.annotation.NonNull; 6 | import com.jakewharton.u2020.R; 7 | import com.jakewharton.u2020.data.Injector; 8 | import dagger.ObjectGraph; 9 | 10 | public final class DebugActivity extends Activity { 11 | private ObjectGraph appGraph; 12 | 13 | @Override protected void onCreate(Bundle savedInstanceState) { 14 | super.onCreate(savedInstanceState); 15 | appGraph = Injector.obtain(getApplication()); 16 | setContentView(R.layout.debug_activity); 17 | } 18 | 19 | @Override public Object getSystemService(@NonNull String name) { 20 | if (Injector.matchesService(name)) { 21 | return appGraph; 22 | } 23 | return super.getSystemService(name); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/src/internalDebug/java/com/jakewharton/u2020/ui/DebugUiModule.java: -------------------------------------------------------------------------------- 1 | package com.jakewharton.u2020.ui; 2 | 3 | import com.jakewharton.u2020.IsInstrumentationTest; 4 | import com.jakewharton.u2020.ui.debug.DebugView; 5 | import com.jakewharton.u2020.ui.debug.DebugViewContainer; 6 | import dagger.Module; 7 | import dagger.Provides; 8 | import javax.inject.Singleton; 9 | 10 | @Module( 11 | injects = { 12 | DebugViewContainer.class, 13 | DebugView.class, 14 | }, 15 | complete = false, 16 | library = true, 17 | overrides = true 18 | ) 19 | public class DebugUiModule { 20 | @Provides @Singleton ViewContainer provideViewContainer(DebugViewContainer debugViewContainer, 21 | @IsInstrumentationTest boolean isInstrumentationTest) { 22 | // Do not add the debug controls for when we are running inside of an instrumentation test. 23 | return isInstrumentationTest ? ViewContainer.DEFAULT : debugViewContainer; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/src/internalDebug/java/com/jakewharton/u2020/ui/debug/AnimationSpeedAdapter.java: -------------------------------------------------------------------------------- 1 | package com.jakewharton.u2020.ui.debug; 2 | 3 | import android.content.Context; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | import android.widget.TextView; 8 | import com.jakewharton.u2020.ui.misc.BindableAdapter; 9 | 10 | class AnimationSpeedAdapter extends BindableAdapter { 11 | private static final int[] VALUES = { 12 | 1, 2, 3, 5, 10 13 | }; 14 | 15 | public static int getPositionForValue(int value) { 16 | for (int i = 0; i < VALUES.length; i++) { 17 | if (VALUES[i] == value) { 18 | return i; 19 | } 20 | } 21 | return 0; // Default to 1x if something changes. 22 | } 23 | 24 | AnimationSpeedAdapter(Context context) { 25 | super(context); 26 | } 27 | 28 | @Override public int getCount() { 29 | return VALUES.length; 30 | } 31 | 32 | @Override public Integer getItem(int position) { 33 | return VALUES[position]; 34 | } 35 | 36 | @Override public long getItemId(int position) { 37 | return position; 38 | } 39 | 40 | @Override public View newView(LayoutInflater inflater, int position, ViewGroup container) { 41 | return inflater.inflate(android.R.layout.simple_spinner_item, container, false); 42 | } 43 | 44 | @Override public void bindView(Integer item, int position, View view) { 45 | TextView tv = view.findViewById(android.R.id.text1); 46 | if (item == 1) { 47 | tv.setText("Normal"); 48 | } else { 49 | tv.setText(item + "x slower"); 50 | } 51 | } 52 | 53 | @Override 54 | public View newDropDownView(LayoutInflater inflater, int position, ViewGroup container) { 55 | return inflater.inflate(android.R.layout.simple_spinner_dropdown_item, container, false); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /app/src/internalDebug/java/com/jakewharton/u2020/ui/debug/DrawerLayoutCompatApi21.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | 18 | package com.jakewharton.u2020.ui.debug; 19 | 20 | import android.annotation.SuppressLint; 21 | import android.annotation.TargetApi; 22 | import android.content.Context; 23 | import android.content.res.TypedArray; 24 | import android.graphics.drawable.Drawable; 25 | import android.os.Build; 26 | import android.view.Gravity; 27 | import android.view.View; 28 | import android.view.ViewGroup; 29 | import android.view.WindowInsets; 30 | 31 | /** 32 | * Provides functionality for DrawerLayout unique to API 21 33 | */ 34 | @TargetApi(Build.VERSION_CODES.LOLLIPOP) 35 | @SuppressLint("RtlHardcoded") 36 | class DrawerLayoutCompatApi21 { 37 | 38 | private static final int[] THEME_ATTRS = { 39 | android.R.attr.colorPrimaryDark 40 | }; 41 | 42 | public static void configureApplyInsets(View drawerLayout) { 43 | if (drawerLayout instanceof DrawerLayoutImpl) { 44 | drawerLayout.setOnApplyWindowInsetsListener(new InsetsListener()); 45 | drawerLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE 46 | | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); 47 | } 48 | } 49 | 50 | public static void dispatchChildInsets(View child, Object insets, int gravity) { 51 | WindowInsets wi = (WindowInsets) insets; 52 | if (gravity == Gravity.LEFT) { 53 | wi = wi.replaceSystemWindowInsets(wi.getSystemWindowInsetLeft(), 54 | wi.getSystemWindowInsetTop(), 0, wi.getSystemWindowInsetBottom()); 55 | } else if (gravity == Gravity.RIGHT) { 56 | wi = wi.replaceSystemWindowInsets(0, wi.getSystemWindowInsetTop(), 57 | wi.getSystemWindowInsetRight(), wi.getSystemWindowInsetBottom()); 58 | } 59 | child.dispatchApplyWindowInsets(wi); 60 | } 61 | 62 | public static void applyMarginInsets(ViewGroup.MarginLayoutParams lp, Object insets, 63 | int gravity) { 64 | WindowInsets wi = (WindowInsets) insets; 65 | if (gravity == Gravity.LEFT) { 66 | wi = wi.replaceSystemWindowInsets(wi.getSystemWindowInsetLeft(), 67 | wi.getSystemWindowInsetTop(), 0, wi.getSystemWindowInsetBottom()); 68 | } else if (gravity == Gravity.RIGHT) { 69 | wi = wi.replaceSystemWindowInsets(0, wi.getSystemWindowInsetTop(), 70 | wi.getSystemWindowInsetRight(), wi.getSystemWindowInsetBottom()); 71 | } 72 | lp.leftMargin = wi.getSystemWindowInsetLeft(); 73 | lp.topMargin = wi.getSystemWindowInsetTop(); 74 | lp.rightMargin = wi.getSystemWindowInsetRight(); 75 | lp.bottomMargin = wi.getSystemWindowInsetBottom(); 76 | } 77 | 78 | public static int getTopInset(Object insets) { 79 | return insets != null ? ((WindowInsets) insets).getSystemWindowInsetTop() : 0; 80 | } 81 | 82 | public static Drawable getDefaultStatusBarBackground(Context context) { 83 | final TypedArray a = context.obtainStyledAttributes(THEME_ATTRS); 84 | try { 85 | return a.getDrawable(0); 86 | } finally { 87 | a.recycle(); 88 | } 89 | } 90 | 91 | static class InsetsListener implements View.OnApplyWindowInsetsListener { 92 | @Override 93 | public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) { 94 | final DrawerLayoutImpl drawerLayout = (DrawerLayoutImpl) v; 95 | drawerLayout.setChildInsets(insets, insets.getSystemWindowInsetTop() > 0); 96 | return insets.consumeSystemWindowInsets(); 97 | } 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /app/src/internalDebug/java/com/jakewharton/u2020/ui/debug/DrawerLayoutImpl.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2014 The Android Open Source Project 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | 18 | package com.jakewharton.u2020.ui.debug; 19 | 20 | /** 21 | * Interface used to communicate from the v21-specific code for configuring a DrawerLayout 22 | * to the DrawerLayout itself. 23 | */ 24 | interface DrawerLayoutImpl { 25 | void setChildInsets(Object insets, boolean drawStatusBar); 26 | } 27 | -------------------------------------------------------------------------------- /app/src/internalDebug/java/com/jakewharton/u2020/ui/debug/HierarchyTreeChangeListener.java: -------------------------------------------------------------------------------- 1 | package com.jakewharton.u2020.ui.debug; 2 | 3 | import android.view.View; 4 | import android.view.ViewGroup; 5 | 6 | /** 7 | * A {@link android.view.ViewGroup.OnHierarchyChangeListener hierarchy change listener} which recursively 8 | * monitors an entire tree of views. 9 | */ 10 | public final class HierarchyTreeChangeListener implements ViewGroup.OnHierarchyChangeListener { 11 | /** 12 | * Wrap a regular {@link android.view.ViewGroup.OnHierarchyChangeListener hierarchy change listener} with one 13 | * that monitors an entire tree of views. 14 | */ 15 | public static HierarchyTreeChangeListener wrap(ViewGroup.OnHierarchyChangeListener delegate) { 16 | return new HierarchyTreeChangeListener(delegate); 17 | } 18 | 19 | private final ViewGroup.OnHierarchyChangeListener delegate; 20 | 21 | private HierarchyTreeChangeListener(ViewGroup.OnHierarchyChangeListener delegate) { 22 | if (delegate == null) { 23 | throw new NullPointerException("Delegate must not be null."); 24 | } 25 | this.delegate = delegate; 26 | } 27 | 28 | @Override public void onChildViewAdded(View parent, View child) { 29 | delegate.onChildViewAdded(parent, child); 30 | 31 | if (child instanceof ViewGroup) { 32 | ViewGroup childGroup = (ViewGroup) child; 33 | childGroup.setOnHierarchyChangeListener(this); 34 | for (int i = 0; i < childGroup.getChildCount(); i++) { 35 | onChildViewAdded(childGroup, childGroup.getChildAt(i)); 36 | } 37 | } 38 | } 39 | 40 | @Override public void onChildViewRemoved(View parent, View child) { 41 | if (child instanceof ViewGroup) { 42 | ViewGroup childGroup = (ViewGroup) child; 43 | for (int i = 0; i < childGroup.getChildCount(); i++) { 44 | onChildViewRemoved(childGroup, childGroup.getChildAt(i)); 45 | } 46 | childGroup.setOnHierarchyChangeListener(null); 47 | } 48 | 49 | delegate.onChildViewRemoved(parent, child); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /app/src/internalDebug/java/com/jakewharton/u2020/ui/debug/NetworkDelayAdapter.java: -------------------------------------------------------------------------------- 1 | package com.jakewharton.u2020.ui.debug; 2 | 3 | import android.content.Context; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | import android.widget.TextView; 8 | import com.jakewharton.u2020.ui.misc.BindableAdapter; 9 | 10 | class NetworkDelayAdapter extends BindableAdapter { 11 | private static final long[] VALUES = { 12 | 250, 500, 1000, 2000, 3000, 5000 13 | }; 14 | 15 | public static int getPositionForValue(long value) { 16 | for (int i = 0; i < VALUES.length; i++) { 17 | if (VALUES[i] == value) { 18 | return i; 19 | } 20 | } 21 | return 3; // Default to 2000 if something changes. 22 | } 23 | 24 | NetworkDelayAdapter(Context context) { 25 | super(context); 26 | } 27 | 28 | @Override public int getCount() { 29 | return VALUES.length; 30 | } 31 | 32 | @Override public Long getItem(int position) { 33 | return VALUES[position]; 34 | } 35 | 36 | @Override public long getItemId(int position) { 37 | return position; 38 | } 39 | 40 | @Override public View newView(LayoutInflater inflater, int position, ViewGroup container) { 41 | return inflater.inflate(android.R.layout.simple_spinner_item, container, false); 42 | } 43 | 44 | @Override public void bindView(Long item, int position, View view) { 45 | TextView tv = view.findViewById(android.R.id.text1); 46 | tv.setText(item + "ms"); 47 | } 48 | 49 | @Override 50 | public View newDropDownView(LayoutInflater inflater, int position, ViewGroup container) { 51 | return inflater.inflate(android.R.layout.simple_spinner_dropdown_item, container, false); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /app/src/internalDebug/java/com/jakewharton/u2020/ui/debug/NetworkPercentageAdapter.java: -------------------------------------------------------------------------------- 1 | package com.jakewharton.u2020.ui.debug; 2 | 3 | import android.content.Context; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | import android.widget.TextView; 8 | import com.jakewharton.u2020.ui.misc.BindableAdapter; 9 | 10 | class NetworkPercentageAdapter extends BindableAdapter { 11 | private static final int[] VALUES = { 12 | 0, 1, 3, 10, 25, 50, 75, 100 13 | }; 14 | 15 | static int getPositionForValue(int value) { 16 | for (int i = 0; i < VALUES.length; i++) { 17 | if (VALUES[i] == value) { 18 | return i; 19 | } 20 | } 21 | return 1; // Default to 3% if something changes. 22 | } 23 | 24 | NetworkPercentageAdapter(Context context) { 25 | super(context); 26 | } 27 | 28 | @Override public int getCount() { 29 | return VALUES.length; 30 | } 31 | 32 | @Override public Integer getItem(int position) { 33 | return VALUES[position]; 34 | } 35 | 36 | @Override public long getItemId(int position) { 37 | return position; 38 | } 39 | 40 | @Override public View newView(LayoutInflater inflater, int position, ViewGroup container) { 41 | return inflater.inflate(android.R.layout.simple_spinner_item, container, false); 42 | } 43 | 44 | @Override public void bindView(Integer item, int position, View view) { 45 | TextView tv = view.findViewById(android.R.id.text1); 46 | if (item == 0) { 47 | tv.setText("None"); 48 | } else { 49 | tv.setText(item + "%"); 50 | } 51 | } 52 | 53 | @Override 54 | public View newDropDownView(LayoutInflater inflater, int position, ViewGroup container) { 55 | return inflater.inflate(android.R.layout.simple_spinner_dropdown_item, container, false); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /app/src/internalDebug/java/com/jakewharton/u2020/ui/debug/NetworkVarianceAdapter.java: -------------------------------------------------------------------------------- 1 | package com.jakewharton.u2020.ui.debug; 2 | 3 | import android.content.Context; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | import android.widget.TextView; 8 | import com.jakewharton.u2020.ui.misc.BindableAdapter; 9 | 10 | class NetworkVarianceAdapter extends BindableAdapter { 11 | private static final int[] VALUES = { 12 | 20, 40, 60 13 | }; 14 | 15 | public static int getPositionForValue(int value) { 16 | for (int i = 0; i < VALUES.length; i++) { 17 | if (VALUES[i] == value) { 18 | return i; 19 | } 20 | } 21 | return 1; // Default to 40% if something changes. 22 | } 23 | 24 | NetworkVarianceAdapter(Context context) { 25 | super(context); 26 | } 27 | 28 | @Override public int getCount() { 29 | return VALUES.length; 30 | } 31 | 32 | @Override public Integer getItem(int position) { 33 | return VALUES[position]; 34 | } 35 | 36 | @Override public long getItemId(int position) { 37 | return position; 38 | } 39 | 40 | @Override public View newView(LayoutInflater inflater, int position, ViewGroup container) { 41 | return inflater.inflate(android.R.layout.simple_spinner_item, container, false); 42 | } 43 | 44 | @Override public void bindView(Integer item, int position, View view) { 45 | TextView tv = view.findViewById(android.R.id.text1); 46 | tv.setText("±" + item + "%"); 47 | } 48 | 49 | @Override 50 | public View newDropDownView(LayoutInflater inflater, int position, ViewGroup container) { 51 | return inflater.inflate(android.R.layout.simple_spinner_dropdown_item, container, false); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /app/src/internalDebug/java/com/jakewharton/u2020/ui/debug/NonConsumingScrimInsetsFrameLayout.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2014 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | package com.jakewharton.u2020.ui.debug; 17 | 18 | import android.content.Context; 19 | import android.content.res.TypedArray; 20 | import android.graphics.Canvas; 21 | import android.graphics.Rect; 22 | import android.graphics.drawable.Drawable; 23 | import android.support.annotation.NonNull; 24 | import android.support.v4.view.ViewCompat; 25 | import android.util.AttributeSet; 26 | import android.widget.FrameLayout; 27 | import com.jakewharton.u2020.R; 28 | 29 | /** 30 | * A layout that draws something in the insets passed to {@link #fitSystemWindows(Rect)}, i.e. the 31 | * area above UI chrome (status and navigation bars, overlay action bars). 32 | *

33 | * Unlike the {@code ScrimInsetsFrameLayout} in the design support library, this variant does not 34 | * consume the insets. 35 | */ 36 | public final class NonConsumingScrimInsetsFrameLayout extends FrameLayout { 37 | private Drawable insetForeground; 38 | private Rect insets; 39 | private Rect tempRect = new Rect(); 40 | 41 | public NonConsumingScrimInsetsFrameLayout(Context context) { 42 | super(context); 43 | init(context, null, 0); 44 | } 45 | 46 | public NonConsumingScrimInsetsFrameLayout(Context context, AttributeSet attrs) { 47 | super(context, attrs); 48 | init(context, attrs, 0); 49 | } 50 | 51 | public NonConsumingScrimInsetsFrameLayout(Context context, AttributeSet attrs, int defStyle) { 52 | super(context, attrs, defStyle); 53 | init(context, attrs, defStyle); 54 | } 55 | 56 | private void init(Context context, AttributeSet attrs, int defStyle) { 57 | TypedArray a = 58 | context.obtainStyledAttributes(attrs, R.styleable.NonConsumingScrimInsetsView, defStyle, 0); 59 | if (a == null) { 60 | return; 61 | } 62 | insetForeground = a.getDrawable(R.styleable.NonConsumingScrimInsetsView_insetForeground); 63 | a.recycle(); 64 | 65 | setWillNotDraw(true); 66 | } 67 | 68 | @Override protected boolean fitSystemWindows(@NonNull Rect insets) { 69 | this.insets = new Rect(insets); 70 | setWillNotDraw(insetForeground == null); 71 | ViewCompat.postInvalidateOnAnimation(this); 72 | return false; // Do not consume insets. 73 | } 74 | 75 | @Override public void draw(@NonNull Canvas canvas) { 76 | super.draw(canvas); 77 | 78 | int width = getWidth(); 79 | int height = getHeight(); 80 | if (insets != null && insetForeground != null) { 81 | int sc = canvas.save(); 82 | canvas.translate(getScrollX(), getScrollY()); 83 | 84 | // Top 85 | tempRect.set(0, 0, width, insets.top); 86 | insetForeground.setBounds(tempRect); 87 | insetForeground.draw(canvas); 88 | 89 | // Bottom 90 | tempRect.set(0, height - insets.bottom, width, height); 91 | insetForeground.setBounds(tempRect); 92 | insetForeground.draw(canvas); 93 | 94 | // Left 95 | tempRect.set(0, insets.top, insets.left, height - insets.bottom); 96 | insetForeground.setBounds(tempRect); 97 | insetForeground.draw(canvas); 98 | 99 | // Right 100 | tempRect.set(width - insets.right, insets.top, width, height - insets.bottom); 101 | insetForeground.setBounds(tempRect); 102 | insetForeground.draw(canvas); 103 | 104 | canvas.restoreToCount(sc); 105 | } 106 | } 107 | 108 | @Override protected void onAttachedToWindow() { 109 | super.onAttachedToWindow(); 110 | if (insetForeground != null) { 111 | insetForeground.setCallback(this); 112 | } 113 | } 114 | 115 | @Override protected void onDetachedFromWindow() { 116 | super.onDetachedFromWindow(); 117 | if (insetForeground != null) { 118 | insetForeground.setCallback(null); 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /app/src/internalDebug/java/com/jakewharton/u2020/ui/debug/ProxyAdapter.java: -------------------------------------------------------------------------------- 1 | package com.jakewharton.u2020.ui.debug; 2 | 3 | import android.content.Context; 4 | import android.view.LayoutInflater; 5 | import android.view.View; 6 | import android.view.ViewGroup; 7 | import android.widget.TextView; 8 | import com.f2prateek.rx.preferences.Preference; 9 | import com.jakewharton.u2020.ui.misc.BindableAdapter; 10 | import java.net.InetSocketAddress; 11 | 12 | class ProxyAdapter extends BindableAdapter { 13 | public static final int NONE = 0; 14 | public static final int PROXY = 1; 15 | 16 | private final Preference proxyAddress; 17 | 18 | ProxyAdapter(Context context, Preference proxyAddress) { 19 | super(context); 20 | if (proxyAddress == null) { 21 | throw new IllegalStateException("proxy == null"); 22 | } 23 | this.proxyAddress = proxyAddress; 24 | } 25 | 26 | @Override public int getCount() { 27 | return 2 /* "None" and "Set" */ + (proxyAddress.isSet() ? 1 : 0); 28 | } 29 | 30 | @Override public String getItem(int position) { 31 | if (position == 0) { 32 | return "None"; 33 | } 34 | if (position == getCount() - 1) { 35 | return "Set…"; 36 | } 37 | return proxyAddress.get().toString(); 38 | } 39 | 40 | @Override public long getItemId(int position) { 41 | return position; 42 | } 43 | 44 | @Override public View newView(LayoutInflater inflater, int position, ViewGroup container) { 45 | return inflater.inflate(android.R.layout.simple_spinner_item, container, false); 46 | } 47 | 48 | @Override public void bindView(String item, int position, View view) { 49 | TextView tv = view.findViewById(android.R.id.text1); 50 | tv.setText(item); 51 | } 52 | 53 | @Override 54 | public View newDropDownView(LayoutInflater inflater, int position, ViewGroup container) { 55 | return inflater.inflate(android.R.layout.simple_spinner_dropdown_item, container, false); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /app/src/internalDebug/java/com/jakewharton/u2020/ui/logs/LogAdapter.java: -------------------------------------------------------------------------------- 1 | package com.jakewharton.u2020.ui.logs; 2 | 3 | import android.content.Context; 4 | import android.support.annotation.DrawableRes; 5 | import android.util.Log; 6 | import android.view.LayoutInflater; 7 | import android.view.View; 8 | import android.view.ViewGroup; 9 | import android.widget.TextView; 10 | import butterknife.BindView; 11 | import butterknife.ButterKnife; 12 | import com.jakewharton.u2020.R; 13 | import com.jakewharton.u2020.ui.misc.BindableAdapter; 14 | import java.util.ArrayList; 15 | import java.util.Collections; 16 | import java.util.List; 17 | import rx.functions.Action1; 18 | 19 | import static com.jakewharton.u2020.data.LumberYard.Entry; 20 | 21 | final class LogAdapter extends BindableAdapter implements Action1 { 22 | private List logs; 23 | 24 | public LogAdapter(Context context) { 25 | super(context); 26 | logs = Collections.emptyList(); 27 | } 28 | 29 | public void setLogs(List logs) { 30 | this.logs = new ArrayList<>(logs); 31 | } 32 | 33 | @Override public void call(Entry entry) { 34 | logs.add(entry); 35 | notifyDataSetChanged(); 36 | } 37 | 38 | @Override public int getCount() { 39 | return logs.size(); 40 | } 41 | 42 | @Override public Entry getItem(int i) { 43 | return logs.get(i); 44 | } 45 | 46 | @Override public long getItemId(int i) { 47 | return i; 48 | } 49 | 50 | @Override public View newView(LayoutInflater inflater, int position, ViewGroup container) { 51 | View view = inflater.inflate(R.layout.debug_logs_list_item, container, false); 52 | LogItemViewHolder viewHolder = new LogItemViewHolder(view); 53 | view.setTag(viewHolder); 54 | return view; 55 | } 56 | 57 | @Override public void bindView(Entry item, int position, View view) { 58 | LogItemViewHolder viewHolder = (LogItemViewHolder) view.getTag(); 59 | viewHolder.setEntry(item); 60 | } 61 | 62 | static final class LogItemViewHolder { 63 | private final View rootView; 64 | @BindView(R.id.debug_log_level) TextView levelView; 65 | @BindView(R.id.debug_log_tag) TextView tagView; 66 | @BindView(R.id.debug_log_message) TextView messageView; 67 | 68 | public LogItemViewHolder(View rootView) { 69 | this.rootView = rootView; 70 | ButterKnife.bind(this, rootView); 71 | } 72 | 73 | public void setEntry(Entry entry) { 74 | rootView.setBackgroundResource(backgroundForLevel(entry.level)); 75 | levelView.setText(entry.displayLevel()); 76 | tagView.setText(entry.tag); 77 | messageView.setText(entry.message); 78 | } 79 | } 80 | 81 | public static @DrawableRes int backgroundForLevel(int level) { 82 | switch (level) { 83 | case Log.VERBOSE: 84 | case Log.DEBUG: 85 | return R.color.debug_log_accent_debug; 86 | case Log.INFO: 87 | return R.color.debug_log_accent_info; 88 | case Log.WARN: 89 | return R.color.debug_log_accent_warn; 90 | case Log.ERROR: 91 | case Log.ASSERT: 92 | return R.color.debug_log_accent_error; 93 | default: 94 | return R.color.debug_log_accent_unknown; 95 | } 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /app/src/internalDebug/java/com/jakewharton/u2020/ui/logs/LogsDialog.java: -------------------------------------------------------------------------------- 1 | package com.jakewharton.u2020.ui.logs; 2 | 3 | import android.content.Context; 4 | import android.content.DialogInterface; 5 | import android.content.Intent; 6 | import android.net.Uri; 7 | import android.support.v7.app.AlertDialog; 8 | import android.widget.ListView; 9 | import android.widget.Toast; 10 | import com.jakewharton.u2020.data.LumberYard; 11 | import com.jakewharton.u2020.util.Intents; 12 | import java.io.File; 13 | import rx.Subscriber; 14 | import rx.android.schedulers.AndroidSchedulers; 15 | import rx.schedulers.Schedulers; 16 | import rx.subscriptions.CompositeSubscription; 17 | 18 | public final class LogsDialog extends AlertDialog { 19 | private final LumberYard lumberYard; 20 | private final LogAdapter adapter; 21 | 22 | private CompositeSubscription subscriptions; 23 | 24 | public LogsDialog(Context context, LumberYard lumberYard) { 25 | super(context); 26 | this.lumberYard = lumberYard; 27 | 28 | adapter = new LogAdapter(context); 29 | 30 | ListView listView = new ListView(context); 31 | listView.setTranscriptMode(ListView.TRANSCRIPT_MODE_NORMAL); 32 | listView.setAdapter(adapter); 33 | 34 | setTitle("Logs"); 35 | setView(listView); 36 | setButton(BUTTON_NEGATIVE, "Close", (dialog, which) -> { 37 | // NO-OP. 38 | }); 39 | setButton(BUTTON_POSITIVE, "Share", (dialog, which) -> { 40 | share(); 41 | }); 42 | } 43 | 44 | @Override protected void onStart() { 45 | super.onStart(); 46 | 47 | adapter.setLogs(lumberYard.bufferedLogs()); 48 | 49 | subscriptions = new CompositeSubscription(); 50 | subscriptions.add(lumberYard.logs() // 51 | .observeOn(AndroidSchedulers.mainThread()) // 52 | .subscribe(adapter)); 53 | } 54 | 55 | @Override protected void onStop() { 56 | super.onStop(); 57 | subscriptions.unsubscribe(); 58 | } 59 | 60 | private void share() { 61 | lumberYard.save() // 62 | .subscribeOn(Schedulers.io()) // 63 | .observeOn(AndroidSchedulers.mainThread()) // 64 | .subscribe(new Subscriber() { 65 | @Override public void onCompleted() { 66 | // NO-OP. 67 | } 68 | 69 | @Override public void onError(Throwable e) { 70 | Toast.makeText(getContext(), "Couldn't save the logs for sharing.", Toast.LENGTH_SHORT) 71 | .show(); 72 | } 73 | 74 | @Override public void onNext(File file) { 75 | Intent sendIntent = new Intent(Intent.ACTION_SEND); 76 | sendIntent.setType("text/plain"); 77 | sendIntent.putExtra(Intent.EXTRA_STREAM, Uri.fromFile(file)); 78 | Intents.maybeStartChooser(getContext(), sendIntent); 79 | } 80 | }); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /app/src/internalDebug/java/com/jakewharton/u2020/ui/trending/ScrollBottomTrendingDebugAction.java: -------------------------------------------------------------------------------- 1 | package com.jakewharton.u2020.ui.trending; 2 | 3 | import com.jakewharton.u2020.ui.debug.ContextualDebugActions.DebugAction; 4 | import javax.inject.Inject; 5 | import javax.inject.Singleton; 6 | 7 | @Singleton public final class ScrollBottomTrendingDebugAction extends DebugAction { 8 | 9 | @Inject public ScrollBottomTrendingDebugAction() { 10 | super(TrendingView.class); 11 | } 12 | 13 | @Override public String name() { 14 | return "Scroll to bottom"; 15 | } 16 | 17 | @Override public void run(TrendingView view) { 18 | view.trendingView.smoothScrollToPosition(view.trendingView.getAdapter().getItemCount() - 1); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/src/internalDebug/java/com/jakewharton/u2020/ui/trending/ScrollTopTrendingDebugAction.java: -------------------------------------------------------------------------------- 1 | package com.jakewharton.u2020.ui.trending; 2 | 3 | import com.jakewharton.u2020.ui.debug.ContextualDebugActions.DebugAction; 4 | import javax.inject.Inject; 5 | import javax.inject.Singleton; 6 | 7 | @Singleton public final class ScrollTopTrendingDebugAction extends DebugAction { 8 | 9 | @Inject public ScrollTopTrendingDebugAction() { 10 | super(TrendingView.class); 11 | } 12 | 13 | @Override public String name() { 14 | return "Scroll to top"; 15 | } 16 | 17 | @Override public void run(TrendingView view) { 18 | view.trendingView.smoothScrollToPosition(0); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/src/internalDebug/java/com/jakewharton/u2020/util/EmptyActivityLifecycleCallbacks.java: -------------------------------------------------------------------------------- 1 | package com.jakewharton.u2020.util; 2 | 3 | import android.app.Activity; 4 | import android.app.Application.ActivityLifecycleCallbacks; 5 | import android.os.Bundle; 6 | 7 | public abstract class EmptyActivityLifecycleCallbacks implements 8 | ActivityLifecycleCallbacks { 9 | @Override public void onActivityCreated(Activity activity, Bundle savedInstanceState) {} 10 | @Override public void onActivityStarted(Activity activity) {} 11 | @Override public void onActivityResumed(Activity activity) {} 12 | @Override public void onActivityPaused(Activity activity) {} 13 | @Override public void onActivityStopped(Activity activity) {} 14 | @Override public void onActivitySaveInstanceState(Activity activity, Bundle outState) {} 15 | @Override public void onActivityDestroyed(Activity activity) {} 16 | } 17 | -------------------------------------------------------------------------------- /app/src/internalDebug/java/com/jakewharton/u2020/util/Keyboards.java: -------------------------------------------------------------------------------- 1 | package com.jakewharton.u2020.util; 2 | 3 | import android.content.Context; 4 | import android.view.View; 5 | import android.view.inputmethod.InputMethodManager; 6 | 7 | public final class Keyboards { 8 | public static void showKeyboard(View view) { 9 | getInputManager(view.getContext()).showSoftInput(view, 0); 10 | } 11 | 12 | private static InputMethodManager getInputManager(Context context) { 13 | return (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); 14 | } 15 | 16 | private Keyboards() { 17 | throw new AssertionError("No instances."); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /app/src/internalDebug/res/drawable-hdpi/debug_drawer_header_bg.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeWharton/u2020/af670f964a4750707d98cda31cfa95244eb3ec07/app/src/internalDebug/res/drawable-hdpi/debug_drawer_header_bg.9.png -------------------------------------------------------------------------------- /app/src/internalDebug/res/drawable-hdpi/debug_drawer_icon_edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeWharton/u2020/af670f964a4750707d98cda31cfa95244eb3ec07/app/src/internalDebug/res/drawable-hdpi/debug_drawer_icon_edit.png -------------------------------------------------------------------------------- /app/src/internalDebug/res/drawable-hdpi/debug_drawer_shadow.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeWharton/u2020/af670f964a4750707d98cda31cfa95244eb3ec07/app/src/internalDebug/res/drawable-hdpi/debug_drawer_shadow.9.png -------------------------------------------------------------------------------- /app/src/internalDebug/res/drawable-hdpi/dev_app_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeWharton/u2020/af670f964a4750707d98cda31cfa95244eb3ec07/app/src/internalDebug/res/drawable-hdpi/dev_app_icon.png -------------------------------------------------------------------------------- /app/src/internalDebug/res/drawable-mdpi/debug_drawer_header_bg.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeWharton/u2020/af670f964a4750707d98cda31cfa95244eb3ec07/app/src/internalDebug/res/drawable-mdpi/debug_drawer_header_bg.9.png -------------------------------------------------------------------------------- /app/src/internalDebug/res/drawable-mdpi/debug_drawer_icon_edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeWharton/u2020/af670f964a4750707d98cda31cfa95244eb3ec07/app/src/internalDebug/res/drawable-mdpi/debug_drawer_icon_edit.png -------------------------------------------------------------------------------- /app/src/internalDebug/res/drawable-mdpi/debug_drawer_shadow.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeWharton/u2020/af670f964a4750707d98cda31cfa95244eb3ec07/app/src/internalDebug/res/drawable-mdpi/debug_drawer_shadow.9.png -------------------------------------------------------------------------------- /app/src/internalDebug/res/drawable-mdpi/dev_app_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeWharton/u2020/af670f964a4750707d98cda31cfa95244eb3ec07/app/src/internalDebug/res/drawable-mdpi/dev_app_icon.png -------------------------------------------------------------------------------- /app/src/internalDebug/res/drawable-xhdpi/debug_drawer_header_bg.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeWharton/u2020/af670f964a4750707d98cda31cfa95244eb3ec07/app/src/internalDebug/res/drawable-xhdpi/debug_drawer_header_bg.9.png -------------------------------------------------------------------------------- /app/src/internalDebug/res/drawable-xhdpi/debug_drawer_icon_edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeWharton/u2020/af670f964a4750707d98cda31cfa95244eb3ec07/app/src/internalDebug/res/drawable-xhdpi/debug_drawer_icon_edit.png -------------------------------------------------------------------------------- /app/src/internalDebug/res/drawable-xhdpi/debug_drawer_shadow.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeWharton/u2020/af670f964a4750707d98cda31cfa95244eb3ec07/app/src/internalDebug/res/drawable-xhdpi/debug_drawer_shadow.9.png -------------------------------------------------------------------------------- /app/src/internalDebug/res/drawable-xhdpi/dev_app_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeWharton/u2020/af670f964a4750707d98cda31cfa95244eb3ec07/app/src/internalDebug/res/drawable-xhdpi/dev_app_icon.png -------------------------------------------------------------------------------- /app/src/internalDebug/res/drawable-xxhdpi/debug_drawer_icon_edit.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeWharton/u2020/af670f964a4750707d98cda31cfa95244eb3ec07/app/src/internalDebug/res/drawable-xxhdpi/debug_drawer_icon_edit.png -------------------------------------------------------------------------------- /app/src/internalDebug/res/drawable-xxhdpi/debug_drawer_shadow.9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeWharton/u2020/af670f964a4750707d98cda31cfa95244eb3ec07/app/src/internalDebug/res/drawable-xxhdpi/debug_drawer_shadow.9.png -------------------------------------------------------------------------------- /app/src/internalDebug/res/drawable-xxhdpi/dev_app_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeWharton/u2020/af670f964a4750707d98cda31cfa95244eb3ec07/app/src/internalDebug/res/drawable-xxhdpi/dev_app_icon.png -------------------------------------------------------------------------------- /app/src/internalDebug/res/drawable-xxxhdpi/dev_app_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JakeWharton/u2020/af670f964a4750707d98cda31cfa95244eb3ec07/app/src/internalDebug/res/drawable-xxxhdpi/dev_app_icon.png -------------------------------------------------------------------------------- /app/src/internalDebug/res/layout/debug_activity.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/internalDebug/res/layout/debug_activity_frame.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11 | 17 | 22 | 27 | 28 | 29 | 37 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /app/src/internalDebug/res/layout/debug_drawer_contextual_action.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 |