diff --git a/.gitignore b/.gitignore index 218cb37..89aff86 100644 --- a/.gitignore +++ b/.gitignore @@ -24,7 +24,6 @@ gen/ # Local configuration file (sdk path, etc) local.properties -gradle.properties keystore.properties ### Mac diff --git a/README.md b/README.md index 9d41cfc..4f5655e 100644 --- a/README.md +++ b/README.md @@ -1,42 +1,36 @@ ![](/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png) # CoReader One Android App support mutil program language.C C# C++ java swift go php xml json...It's useful for developer to read on the phone. -###Add repo: +### Add repo: * Can download repo from github trending * Search repo in the github * Add repo by name and download url * Open local file. -###Download +### Download [Fir Download](http://fir.im/coreader)Scan the QRCode to install ![](/screenshot/codereader_fir_download_qr.png) -[Download from release](https://github.com/loopeer/code-reader/releases/tag/V1.0.1) +[Download from release](https://github.com/loopeer/code-reader/releases/tag/v1.1.0) Screenshot ==== -###Support multi markdown style +### Support multi markdown style ![](/screenshot/codereader_md.gif) Base on [markdownj](https://github.com/myabc/markdownj).Add many github styles. -###Day night theme +### Day night theme ![](/screenshot/codereader_setting_day_h.gif) ![](/screenshot/codereader_setting_night_h.gif) ![](/screenshot/codereader_setting_day.gif) ![](/screenshot/codereader_setting_night.gif) -###Repo item swipe action(Sea [itemtouchhelper-extension](https://github.com/loopeer/itemtouchhelper-extension)) +### Repo item swipe action(Sea [itemtouchhelper-extension](https://github.com/loopeer/itemtouchhelper-extension)) ![](/screenshot/codereader_itemtouch.gif) -###Download repo from github trending +### Download repo from github trending ![](/screenshot/codereader_trending.gif) -###Search repo in github +### Search repo in github ![](/screenshot/codereader_search.gif) -###Add repo to download -![](/screenshot/codereader_add.gif) - -Collaborators -==== -[kKumaJ](https://github.com/kKumaJ) -[yanxinit](https://github.com/yanxinit) -[ToDou](https://github.com/ToDou) +### Add repo to download +![](/screenshot/codereader_add.gif) Contact: ==== diff --git a/app/build.gradle b/app/build.gradle deleted file mode 100644 index 369a13f..0000000 --- a/app/build.gradle +++ /dev/null @@ -1,51 +0,0 @@ -apply plugin: 'com.android.application' -apply plugin: 'android-apt' -apply plugin: 'com.squareup.sqldelight' -apply plugin: 'com.neenbedankt.android-apt' -apply plugin: 'me.tatarka.retrolambda' - -android { - compileSdkVersion Integer.parseInt(compile_sdk_version) - buildToolsVersion build_tools_version - - defaultConfig { - applicationId "com.loopeer.codereader" - minSdkVersion min_sdk_version - targetSdkVersion target_sdk_version - versionCode Integer.parseInt(version_code) - versionName version_name - } - buildTypes { - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' - } - } - compileOptions { - targetCompatibility 1.8 - sourceCompatibility 1.8 - } -} - -dependencies { - compile fileTree(dir: 'libs', include: ['*.jar']) - testCompile 'junit:junit:4.12' - compile 'com.android.support:appcompat-v7:' + support_version - compile 'com.android.support:design:' + support_version - compile 'com.jakewharton:butterknife:' + butterknife_version - apt 'com.jakewharton:butterknife-compiler:' + butterknife_version - compile 'com.squareup.retrofit2:retrofit:' + retrofit - compile 'com.squareup.retrofit2:converter-gson:' + retrofit - compile 'com.squareup.retrofit2:adapter-rxjava:' + retrofit - compile 'com.squareup.okhttp3:logging-interceptor:3.4.1' - compile 'io.reactivex:rxjava:' + rxjava - compile 'io.reactivex:rxandroid:' + rxandroid - compile 'com.github.bumptech.glide:glide:' + glide - apt 'com.google.auto.value:auto-value:' + auto_value - provided 'com.google.auto.value:auto-value:' + auto_value - provided 'com.jakewharton.auto.value:auto-value-annotations:1.2-update1' - compile project(':libraries:markdownj') - compile project(':libraries:directoryandfilechooser') - compile project(':libraries:itemtouchhelperextension') - debugCompile 'com.facebook.stetho:stetho:1.1.0' -} diff --git a/app/src/androidTest/java/com/loopeer/codereader/ApplicationTest.java b/app/src/androidTest/java/com/loopeer/codereader/ApplicationTest.java deleted file mode 100644 index 89c327a..0000000 --- a/app/src/androidTest/java/com/loopeer/codereader/ApplicationTest.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.loopeer.codereader; - -import android.app.Application; -import android.test.ApplicationTestCase; - -/** - * Testing Fundamentals - */ -public class ApplicationTest extends ApplicationTestCase { - public ApplicationTest() { - super(Application.class); - } -} \ No newline at end of file diff --git a/app/src/main/java/com/loopeer/codereader/CodeReaderApplication.java b/app/src/main/java/com/loopeer/codereader/CodeReaderApplication.java deleted file mode 100644 index a2f440b..0000000 --- a/app/src/main/java/com/loopeer/codereader/CodeReaderApplication.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.loopeer.codereader; - -import android.app.Application; -import android.content.Context; -import android.support.v7.app.AppCompatDelegate; - -import com.facebook.stetho.Stetho; -import com.loopeer.codereader.utils.ThemeUtils; - -public class CodeReaderApplication extends Application { - private static CodeReaderApplication mInstance; - private static Context sAppContext; - - @Override - public void onCreate() { - super.onCreate(); - mInstance = this; - sAppContext = getApplicationContext(); - Stetho.initialize( - Stetho.newInitializerBuilder(this) - .enableDumpapp(Stetho.defaultDumperPluginsProvider(this)) - .enableWebKitInspector(Stetho.defaultInspectorModulesProvider(this)) - .build()); - AppCompatDelegate.setDefaultNightMode(ThemeUtils.getCurrentNightMode(this)); - } - - public static Context getAppContext() { - return sAppContext; - } - - public static CodeReaderApplication getInstance() { - return mInstance; - } - -} diff --git a/app/src/main/java/com/loopeer/codereader/Navigator.java b/app/src/main/java/com/loopeer/codereader/Navigator.java deleted file mode 100644 index 35e4cb4..0000000 --- a/app/src/main/java/com/loopeer/codereader/Navigator.java +++ /dev/null @@ -1,123 +0,0 @@ -package com.loopeer.codereader; - -import android.app.DownloadManager; -import android.content.Context; -import android.content.Intent; -import android.net.Uri; -import android.text.Html; -import android.widget.Toast; - -import com.loopeer.codereader.coreader.db.CoReaderDbHelper; -import com.loopeer.codereader.model.Repo; -import com.loopeer.codereader.sync.DownloadRepoService; -import com.loopeer.codereader.ui.activity.AboutActivity; -import com.loopeer.codereader.ui.activity.AddRepoActivity; -import com.loopeer.codereader.ui.activity.CodeReadActivity; -import com.loopeer.codereader.ui.activity.MainActivity; -import com.loopeer.codereader.ui.activity.SearchActivity; -import com.loopeer.codereader.ui.activity.SettingActivity; -import com.loopeer.codereader.ui.activity.SimpleWebActivity; - -public class Navigator { - - public final static String EXTRA_REPO = "extra_repo"; - public final static String EXTRA_ID = "extra_id"; - public final static String EXTRA_DOWNLOAD_SERVICE_TYPE = "extra_download_service_type"; - public final static String EXTRA_DIRETORY_ROOT = "extra_diretory_root"; - public final static String EXTRA_DIRETORY_ROOT_NODE_INSTANCE = "extra_diretory_root_node_instance"; - public final static String EXTRA_DIRETORY_SELECTING = "extra_diretory_selecting"; - public final static String EXTRA_WEB_URL = "extra_web_url"; - public final static String EXTRA_HTML_STRING = "extra_html_string"; - - public static void startMainActivity(Context context) { - Intent intent = new Intent(context, MainActivity.class); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); - context.startActivity(intent); - } - - public static void startCodeReadActivity(Context context, Repo repo) { - Intent intent = new Intent(context, CodeReadActivity.class); - intent.putExtra(EXTRA_REPO, repo); - context.startActivity(intent); - } - - public static void startWebActivity(Context context, String url) { - Intent intent = new Intent(context, SimpleWebActivity.class); - intent.putExtra(EXTRA_WEB_URL, url); - context.startActivity(intent); - } - - public static void startAboutActivity(Context context) { - Intent intent = new Intent(context, AboutActivity.class); - context.startActivity(intent); - } - - public static void startComposeEmail(Context context, String[] addresses, String subject, String content) { - Intent intent = new Intent(Intent.ACTION_SENDTO, Uri.parse("mailto:")); - intent.putExtra(Intent.EXTRA_SUBJECT, subject); - intent.putExtra(Intent.EXTRA_EMAIL, addresses); - intent.putExtra(Intent.EXTRA_TEXT, Html.fromHtml(content)); - if (intent.resolveActivity(context.getPackageManager()) != null) { - context.startActivity(intent); - } else { - Toast.makeText(context, R.string.about_email_app_not_have, Toast.LENGTH_SHORT).show(); - } - } - - public static void startDownloadNewRepoService(Context context, Repo repo) { - Repo sameRepo = CoReaderDbHelper.getInstance(context).readSameRepo(repo); - long repoId; - if (sameRepo != null) { - repoId = Long.parseLong(sameRepo.id); - } else { - repoId = CoReaderDbHelper.getInstance(context).insertRepo(repo); - } - repo.id = String.valueOf(repoId); - Navigator.startDownloadRepoService(context, repo); - } - - public static void startDownloadRepoService(Context context, Repo repo) { - Intent intent = new Intent(context, DownloadRepoService.class); - intent.putExtra(EXTRA_REPO, repo); - intent.putExtra(EXTRA_DOWNLOAD_SERVICE_TYPE, DownloadRepoService.DOWNLOAD_REPO); - context.startService(intent); - } - - public static void startDownloadRepoService(Context context, int type) { - Intent intent = new Intent(context, DownloadRepoService.class); - intent.putExtra(EXTRA_DOWNLOAD_SERVICE_TYPE, type); - context.startService(intent); - } - - public static void startDownloadRepoServiceRemove(Context context, long downloadId) { - Intent intent = new Intent(context, DownloadRepoService.class); - intent.putExtra(EXTRA_DOWNLOAD_SERVICE_TYPE, DownloadRepoService.DOWNLOAD_REMOVE_DOWNLOAD); - intent.putExtra(DownloadManager.EXTRA_DOWNLOAD_ID, downloadId); - context.startService(intent); - } - - public static void startSearchActivity(Context context) { - Intent intent = new Intent(context, SearchActivity.class); - context.startActivity(intent); - } - - public static void startAddRepoActivity(Context context) { - Intent intent = new Intent(context, AddRepoActivity.class); - context.startActivity(intent); - } - - public static void startSettingActivity(Context context) { - Intent intent = new Intent(context, SettingActivity.class); - context.startActivity(intent); - } - - public static void startOutWebActivity(Context context, String url) { - Intent intent = new Intent(); - intent.setAction(Intent.ACTION_VIEW); - Uri content_url = Uri.parse(url); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - intent.setData(content_url); - context.startActivity(intent); - } - -} diff --git a/app/src/main/java/com/loopeer/codereader/api/ApiService.java b/app/src/main/java/com/loopeer/codereader/api/ApiService.java deleted file mode 100644 index e1912db..0000000 --- a/app/src/main/java/com/loopeer/codereader/api/ApiService.java +++ /dev/null @@ -1,82 +0,0 @@ -package com.loopeer.codereader.api; - -import android.app.Application; - -import com.loopeer.codereader.BuildConfig; -import com.loopeer.codereader.CodeReaderApplication; - -import java.io.File; -import java.util.MissingResourceException; -import java.util.concurrent.TimeUnit; - -import okhttp3.Cache; -import okhttp3.OkHttpClient; -import retrofit2.Retrofit; -import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory; -import retrofit2.converter.gson.GsonConverterFactory; - -public class ApiService { - - public static final String API_URL = "https://api.github.com/"; - - private static ApiService sInstance; - - private Retrofit mRetrofit; - - public static synchronized ApiService getInstance() { - if (sInstance == null) { - sInstance = new ApiService(); - } - return sInstance; - } - - - private OkHttpClient getClient() { - return createOkHttpClient(CodeReaderApplication.getInstance()); - } - - static final int DISK_CACHE_SIZE = 50 * 1024 * 1024; // 50MB - - static OkHttpClient createOkHttpClient(Application app) { - OkHttpClient.Builder httpClient = new OkHttpClient.Builder(); - - if (BuildConfig.DEBUG) { - HttpJsonLoggingInterceptor loggingInterceptor = new HttpJsonLoggingInterceptor(); - loggingInterceptor.setLevel(HttpJsonLoggingInterceptor.Level.BODY); - httpClient.addInterceptor(loggingInterceptor); - } - - httpClient.connectTimeout(1, TimeUnit.HOURS); // connect timeout - httpClient.readTimeout(1, TimeUnit.HOURS); - File cacheDir = new File(app.getCacheDir(), "http"); - Cache cache = new Cache(cacheDir, DISK_CACHE_SIZE); - httpClient.cache(cache); - return httpClient.build(); - } - - protected Retrofit.Builder newRestAdapterBuilder() { - return new Retrofit.Builder(); - } - - protected Retrofit getRetrofit() { - if (mRetrofit == null) { - try { - mRetrofit = newRestAdapterBuilder() - .client(getClient()) - .addConverterFactory(GsonConverterFactory.create()) - .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) - .baseUrl(API_URL) - .build(); - } catch (NullPointerException e) { - throw new MissingResourceException("Define your endpoint in api_url string resource.", getClass().getName(), "api_url"); - } - } - - return mRetrofit; - } - - public static T create(Class service) { - return getInstance().getRetrofit().create(service); - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/loopeer/codereader/api/BaseListResponse.java b/app/src/main/java/com/loopeer/codereader/api/BaseListResponse.java deleted file mode 100644 index 17fb08f..0000000 --- a/app/src/main/java/com/loopeer/codereader/api/BaseListResponse.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.loopeer.codereader.api; - -import com.google.gson.annotations.SerializedName; - -import java.util.List; - -public class BaseListResponse { - - @SerializedName("total_count") - public int totalCount; - @SerializedName("incomplete_results") - public boolean incompleteResults; - @SerializedName("items") - public List items; - -} diff --git a/app/src/main/java/com/loopeer/codereader/api/HttpJsonLoggingInterceptor.java b/app/src/main/java/com/loopeer/codereader/api/HttpJsonLoggingInterceptor.java deleted file mode 100644 index fcf097e..0000000 --- a/app/src/main/java/com/loopeer/codereader/api/HttpJsonLoggingInterceptor.java +++ /dev/null @@ -1,320 +0,0 @@ -/* - * Copyright (C) 2015 Square, Inc. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.loopeer.codereader.api; - -import org.json.JSONException; -import org.json.JSONObject; - -import java.io.EOFException; -import java.io.IOException; -import java.nio.charset.Charset; -import java.nio.charset.UnsupportedCharsetException; -import java.util.concurrent.TimeUnit; - -import okhttp3.Connection; -import okhttp3.Headers; -import okhttp3.Interceptor; -import okhttp3.MediaType; -import okhttp3.OkHttpClient; -import okhttp3.Protocol; -import okhttp3.Request; -import okhttp3.RequestBody; -import okhttp3.Response; -import okhttp3.ResponseBody; -import okhttp3.internal.http.HttpHeaders; -import okhttp3.internal.platform.Platform; -import okio.Buffer; -import okio.BufferedSource; - -import static okhttp3.internal.platform.Platform.INFO; - -/** - * An OkHttp interceptor which logs request and response information. Can be applied as an - * {@linkplain OkHttpClient#interceptors() application interceptor} or as a {@linkplain - * OkHttpClient#networkInterceptors() network interceptor}.

The format of the logs created by - * this class should not be considered stable and may change slightly between releases. If you need - * a stable logging format, use your own interceptor. - */ -public final class HttpJsonLoggingInterceptor implements Interceptor { - private static final Charset UTF8 = Charset.forName("UTF-8"); - - public enum Level { - /** - * No logs. - */ - NONE, - /** - * Logs request and response lines. - *

- *

Example: - *

{@code
-         * --> POST /greeting http/1.1 (3-byte body)
-         *
-         * <-- 200 OK (22ms, 6-byte body)
-         * }
- */ - BASIC, - /** - * Logs request and response lines and their respective headers. - *

- *

Example: - *

{@code
-         * --> POST /greeting http/1.1
-         * Host: example.com
-         * Content-Type: plain/text
-         * Content-Length: 3
-         * --> END POST
-         *
-         * <-- 200 OK (22ms)
-         * Content-Type: plain/text
-         * Content-Length: 6
-         * <-- END HTTP
-         * }
- */ - HEADERS, - /** - * Logs request and response lines and their respective headers and bodies (if present). - *

- *

Example: - *

{@code
-         * --> POST /greeting http/1.1
-         * Host: example.com
-         * Content-Type: plain/text
-         * Content-Length: 3
-         *
-         * Hi?
-         * --> END POST
-         *
-         * <-- 200 OK (22ms)
-         * Content-Type: plain/text
-         * Content-Length: 6
-         *
-         * Hello!
-         * <-- END HTTP
-         * }
- */ - BODY - } - - public interface Logger { - void log(String message); - - /** - * A {@link Logger} defaults output appropriate for the current platform. - */ - Logger DEFAULT = new Logger() { - @Override - public void log(String message) { - Platform.get().log(INFO, message, null); - } - }; - } - - public HttpJsonLoggingInterceptor() { - this(Logger.DEFAULT); - } - - public HttpJsonLoggingInterceptor(Logger logger) { - this.logger = logger; - } - - private final Logger logger; - - private volatile Level level = Level.NONE; - - /** - * Change the level at which this interceptor logs. - */ - public HttpJsonLoggingInterceptor setLevel(Level level) { - if (level == null) throw new NullPointerException("level == null. Use Level.NONE instead."); - this.level = level; - return this; - } - - public Level getLevel() { - return level; - } - - @Override - public Response intercept(Chain chain) throws IOException { - Level level = this.level; - - Request request = chain.request(); - if (level == Level.NONE) { - return chain.proceed(request); - } - - boolean logBody = level == Level.BODY; - boolean logHeaders = logBody || level == Level.HEADERS; - - RequestBody requestBody = request.body(); - boolean hasRequestBody = requestBody != null; - - Connection connection = chain.connection(); - Protocol protocol = connection != null ? connection.protocol() : Protocol.HTTP_1_1; - String requestStartMessage = "--> " + request.method() + ' ' + request.url() + ' ' + protocol; - if (!logHeaders && hasRequestBody) { - requestStartMessage += " (" + requestBody.contentLength() + "-byte body)"; - } - logger.log(requestStartMessage); - - if (logHeaders) { - if (hasRequestBody) { - // Request body headers are only present when installed as a network interceptor. Force - // them to be included (when available) so there values are known. - if (requestBody.contentType() != null) { - logger.log("Content-Type: " + requestBody.contentType()); - } - if (requestBody.contentLength() != -1) { - logger.log("Content-Length: " + requestBody.contentLength()); - } - } - - Headers headers = request.headers(); - for (int i = 0, count = headers.size(); i < count; i++) { - String name = headers.name(i); - // Skip headers from the request body as they are explicitly logged above. - if (!"Content-Type".equalsIgnoreCase(name) && !"Content-Length".equalsIgnoreCase(name)) { - logger.log(name + ": " + headers.value(i)); - } - } - - if (!logBody || !hasRequestBody) { - logger.log("--> END " + request.method()); - } else if (bodyEncoded(request.headers())) { - logger.log("--> END " + request.method() + " (encoded body omitted)"); - } else { - Buffer buffer = new Buffer(); - requestBody.writeTo(buffer); - - Charset charset = UTF8; - MediaType contentType = requestBody.contentType(); - if (contentType != null) { - charset = contentType.charset(UTF8); - } - - logger.log(""); - if (isPlaintext(buffer)) { - logger.log(buffer.readString(charset)); - logger.log("--> END " + request.method() - + " (" + requestBody.contentLength() + "-byte body)"); - } else { - logger.log("--> END " + request.method() + " (binary " - + requestBody.contentLength() + "-byte body omitted)"); - } - } - } - - long startNs = System.nanoTime(); - Response response; - try { - response = chain.proceed(request); - } catch (Exception e) { - logger.log("<-- HTTP FAILED: " + e); - throw e; - } - long tookMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs); - - ResponseBody responseBody = response.body(); - long contentLength = responseBody.contentLength(); - String bodySize = contentLength != -1 ? contentLength + "-byte" : "unknown-length"; - logger.log("<-- " + response.code() + ' ' + response.message() + ' ' - + response.request().url() + " (" + tookMs + "ms" + (!logHeaders ? ", " - + bodySize + " body" : "") + ')'); - - if (logHeaders) { - Headers headers = response.headers(); - for (int i = 0, count = headers.size(); i < count; i++) { - logger.log(headers.name(i) + ": " + headers.value(i)); - } - - if (!logBody || !HttpHeaders.hasBody(response)) { - logger.log("<-- END HTTP"); - } else if (bodyEncoded(response.headers())) { - logger.log("<-- END HTTP (encoded body omitted)"); - } else { - BufferedSource source = responseBody.source(); - source.request(Long.MAX_VALUE); // Buffer the entire body. - Buffer buffer = source.buffer(); - - Charset charset = UTF8; - MediaType contentType = responseBody.contentType(); - if (contentType != null) { - try { - charset = contentType.charset(UTF8); - } catch (UnsupportedCharsetException e) { - logger.log(""); - logger.log("Couldn't decode the response body; charset is likely malformed."); - logger.log("<-- END HTTP"); - - return response; - } - } - - if (!isPlaintext(buffer)) { - logger.log(""); - logger.log("<-- END HTTP (binary " + buffer.size() + "-byte body omitted)"); - return response; - } - - if (contentLength != 0) { - logger.log(""); - String logText = buffer.clone().readString(charset); - try { - JSONObject logJson = new JSONObject(logText); - logger.log(logJson.toString(4)); - } catch (JSONException e) { - e.printStackTrace(); - logger.log(logText); - } - } - - logger.log("<-- END HTTP (" + buffer.size() + "-byte body)"); - } - } - - return response; - } - - /** - * Returns true if the body in question probably contains human readable text. Uses a small sample - * of code points to detect unicode control characters commonly used in binary file signatures. - */ - static boolean isPlaintext(Buffer buffer) { - try { - Buffer prefix = new Buffer(); - long byteCount = buffer.size() < 64 ? buffer.size() : 64; - buffer.copyTo(prefix, 0, byteCount); - for (int i = 0; i < 16; i++) { - if (prefix.exhausted()) { - break; - } - int codePoint = prefix.readUtf8CodePoint(); - if (Character.isISOControl(codePoint) && !Character.isWhitespace(codePoint)) { - return false; - } - } - return true; - } catch (EOFException e) { - return false; // Truncated UTF-8 sequence. - } - } - - private boolean bodyEncoded(Headers headers) { - String contentEncoding = headers.get("Content-Encoding"); - return contentEncoding != null && !contentEncoding.equalsIgnoreCase("identity"); - } -} \ No newline at end of file diff --git a/app/src/main/java/com/loopeer/codereader/api/ServiceFactory.java b/app/src/main/java/com/loopeer/codereader/api/ServiceFactory.java deleted file mode 100644 index 09dfa3e..0000000 --- a/app/src/main/java/com/loopeer/codereader/api/ServiceFactory.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.loopeer.codereader.api; - -import com.loopeer.codereader.api.service.GithubService; - -public class ServiceFactory { - - public static GithubService getGithubService() { - return ServiceUtils.getApiService().getRetrofit().create(GithubService.class); - } - -} diff --git a/app/src/main/java/com/loopeer/codereader/api/ServiceUtils.java b/app/src/main/java/com/loopeer/codereader/api/ServiceUtils.java deleted file mode 100644 index a006433..0000000 --- a/app/src/main/java/com/loopeer/codereader/api/ServiceUtils.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.loopeer.codereader.api; - -public class ServiceUtils { - private static ApiService sApiService; - public static synchronized ApiService getApiService() { - if (sApiService == null) { - sApiService = new ApiService(); - } - return sApiService; - } -} diff --git a/app/src/main/java/com/loopeer/codereader/api/service/GithubService.java b/app/src/main/java/com/loopeer/codereader/api/service/GithubService.java deleted file mode 100644 index e97cbbf..0000000 --- a/app/src/main/java/com/loopeer/codereader/api/service/GithubService.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.loopeer.codereader.api.service; - -import com.loopeer.codereader.api.BaseListResponse; -import com.loopeer.codereader.model.Repository; - -import okhttp3.ResponseBody; -import retrofit2.Response; -import retrofit2.http.GET; -import retrofit2.http.Query; -import retrofit2.http.Streaming; -import retrofit2.http.Url; -import rx.Observable; - -public interface GithubService { - - @Streaming - @GET - Observable downloadRepo(@Url String fileUrl); - - @GET("/search/repositories") - Observable>> repositories( - @Query("q") String keyword, - @Query("sort") String sort, - @Query("order") String order, - @Query("page") int page, - @Query("per_page") int pageSize - ); - -} - diff --git a/app/src/main/java/com/loopeer/codereader/coreader/db/CoReaderDbHelper.java b/app/src/main/java/com/loopeer/codereader/coreader/db/CoReaderDbHelper.java deleted file mode 100644 index f4a6e68..0000000 --- a/app/src/main/java/com/loopeer/codereader/coreader/db/CoReaderDbHelper.java +++ /dev/null @@ -1,160 +0,0 @@ -package com.loopeer.codereader.coreader.db; - -import android.content.Context; -import android.database.Cursor; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteOpenHelper; - -import com.loopeer.codereader.model.Repo; - -import java.util.ArrayList; -import java.util.List; - -public class CoReaderDbHelper extends SQLiteOpenHelper { - private static final String TAG = "CoReaderDbHelper"; - - private static final String DATABASE_NAME = "coreader.db"; - private static final int DATABASE_VERSION = 1; - - private static volatile CoReaderDbHelper sInstance = null; - - private CoReaderDbHelper(Context context) { - super(context.getApplicationContext(), DATABASE_NAME, null, DATABASE_VERSION); - } - - public static CoReaderDbHelper getInstance(Context context) { - CoReaderDbHelper inst = sInstance; - if (inst == null) { - synchronized (CoReaderDbHelper.class) { - inst = sInstance; - if (inst == null) { - inst = new CoReaderDbHelper(context); - sInstance = inst; - } - } - } - return inst; - } - - @Override - public void onCreate(SQLiteDatabase db) { - db.execSQL(DbRepoModel.CREATE_TABLE); - } - - @Override - public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { - int version = oldVersion; - - if (version != DATABASE_VERSION) { - db.execSQL("DROP TABLE IF EXISTS " + DbRepoModel.TABLE_NAME); - onCreate(db); - } - } - - public void deleteAllTable() { - SQLiteDatabase db = getWritableDatabase(); - db.delete(DbRepoModel.TABLE_NAME, null, null); - db.close(); - } - - public long insertRepo(Repo repo) { - Repo same = readSameRepo(repo); - if (same != null) return Long.valueOf(same.id); - if (repo.lastModify == 0) repo.lastModify = System.currentTimeMillis(); - SQLiteDatabase db = getWritableDatabase(); - return db.insert(DbRepoModel.TABLE_NAME, null, DbRepo.FACTORY.marshal() - .name(repo.name) - .absolute_path(repo.absolutePath) - .last_modify(repo.lastModify) - .net_url(repo.netDownloadUrl) - .is_folder(repo.isFolder) - .download_id(repo.downloadId) - .factor(repo.factor) - .is_unzip(repo.isUnzip) - .asContentValues()); - } - - private boolean haveSameRepo(Repo repo) { - Repo repo1 = readSameRepo(repo); - if (repo1 != null) return true; - return false; - } - - public Repo readSameRepo(Repo repo) { - SQLiteDatabase db = getReadableDatabase(); - Repo result = null; - Cursor cursor = db.rawQuery( - DbRepo.CHECK_SAME_REPO, - new String[]{ - repo.name, - repo.absolutePath - }); - if (cursor.moveToFirst()) { - result = parseRepo(cursor); - } - return result; - } - - private Repo parseRepo(Cursor cursor) { - DbRepo dbRepo = DbRepo.FOR_TEAM_MAPPER.map(cursor); - Repo repo = new Repo(); - repo.id = String.valueOf(dbRepo._id()); - repo.name = dbRepo.name(); - repo.absolutePath = dbRepo.absolute_path(); - repo.netDownloadUrl = dbRepo.net_url(); - repo.isFolder = dbRepo.is_folder(); - repo.lastModify = dbRepo.last_modify(); - repo.downloadId = dbRepo.download_id(); - repo.factor = dbRepo.factor(); - repo.isUnzip = dbRepo.is_unzip(); - return repo; - } - - public List readRepos() { - SQLiteDatabase db = getReadableDatabase(); - List repos = new ArrayList<>(); - Cursor cursor = db.rawQuery(DbRepo.SELECT_ALL, null); - if (cursor != null && cursor.getCount() > 0) { - while (cursor.moveToNext()) { - Repo repo = parseRepo(cursor); - if (repo != null) { - repos.add(repo); - } - } - } - return repos; - } - - public void updateRepoLastModify(long primaryKey, long lastModify) { - SQLiteDatabase db = getWritableDatabase(); - db.execSQL(DbRepoModel.UPDATE_LAST_MODIFY, new String[]{String.valueOf(lastModify), String.valueOf(primaryKey)}); - } - - public void updateRepoDownloadId(long downloadId, String repoId) { - SQLiteDatabase db = getWritableDatabase(); - db.execSQL(DbRepoModel.UPDATE_DOWNLOAD_ID, new String[]{String.valueOf(downloadId), String.valueOf(repoId)}); - } - - public void resetRepoDownloadId(long downloadId) { - SQLiteDatabase db = getWritableDatabase(); - db.execSQL(DbRepoModel.RESET_DOWNLOAD_ID, new String[]{String.valueOf(downloadId)}); - } - - public void updateRepoDownloadProgress(long downloadId, float factor) { - SQLiteDatabase db = getWritableDatabase(); - db.execSQL(DbRepoModel.UPDATE_DOWNLOAD_PROGRESS - , new String[]{String.valueOf(factor), String.valueOf(downloadId)}); - } - - public void updateRepoUnzipProgress(long downloadId, float factor, boolean isUnzip) { - SQLiteDatabase db = getWritableDatabase(); - db.execSQL(DbRepoModel.UPDATE_UNZIP_PROGRESS - , new String[]{String.valueOf(factor), String.valueOf(isUnzip ? 1 : 0), String.valueOf(downloadId)}); - } - - public void deleteRepo(long id) { - SQLiteDatabase db = getWritableDatabase(); - db.execSQL(DbRepoModel.DELETE_REPO - , new String[]{String.valueOf(id)}); - } -} \ No newline at end of file diff --git a/app/src/main/java/com/loopeer/codereader/event/DownloadFailDeleteEvent.java b/app/src/main/java/com/loopeer/codereader/event/DownloadFailDeleteEvent.java deleted file mode 100644 index 9fa5ca2..0000000 --- a/app/src/main/java/com/loopeer/codereader/event/DownloadFailDeleteEvent.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.loopeer.codereader.event; - -import com.loopeer.codereader.model.Repo; - -public class DownloadFailDeleteEvent { - - public Repo deleteRepo; - - public DownloadFailDeleteEvent(Repo deleteRepo) { - this.deleteRepo = deleteRepo; - } -} - diff --git a/app/src/main/java/com/loopeer/codereader/event/DownloadProgressEvent.java b/app/src/main/java/com/loopeer/codereader/event/DownloadProgressEvent.java deleted file mode 100644 index 8dc7337..0000000 --- a/app/src/main/java/com/loopeer/codereader/event/DownloadProgressEvent.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.loopeer.codereader.event; - -public class DownloadProgressEvent { - - public String repoId; - public long downloadId; - public float factor; - public boolean isUnzip; - - public DownloadProgressEvent(long downloadId, boolean isUnzip) { - this.downloadId = downloadId; - this.factor = 1.f; - this.isUnzip = isUnzip; - } - - public DownloadProgressEvent(String repoId, long downloadId, float factor, boolean isUnzip) { - this.repoId = repoId; - this.downloadId = downloadId; - this.factor = factor; - this.isUnzip = isUnzip; - } -} diff --git a/app/src/main/java/com/loopeer/codereader/event/DownloadRepoMessageEvent.java b/app/src/main/java/com/loopeer/codereader/event/DownloadRepoMessageEvent.java deleted file mode 100644 index 7e485a2..0000000 --- a/app/src/main/java/com/loopeer/codereader/event/DownloadRepoMessageEvent.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.loopeer.codereader.event; - -public class DownloadRepoMessageEvent { - public String message; - - public DownloadRepoMessageEvent(String message) { - this.message = message; - } - - public String getMessage() { - return message; - } -} diff --git a/app/src/main/java/com/loopeer/codereader/event/ThemeRecreateEvent.java b/app/src/main/java/com/loopeer/codereader/event/ThemeRecreateEvent.java deleted file mode 100644 index a52e488..0000000 --- a/app/src/main/java/com/loopeer/codereader/event/ThemeRecreateEvent.java +++ /dev/null @@ -1,4 +0,0 @@ -package com.loopeer.codereader.event; - -public class ThemeRecreateEvent { -} diff --git a/app/src/main/java/com/loopeer/codereader/model/BaseModel.java b/app/src/main/java/com/loopeer/codereader/model/BaseModel.java deleted file mode 100644 index 082b39c..0000000 --- a/app/src/main/java/com/loopeer/codereader/model/BaseModel.java +++ /dev/null @@ -1,7 +0,0 @@ -package com.loopeer.codereader.model; - -import java.io.Serializable; - -public class BaseModel implements Serializable { - public String id; -} diff --git a/app/src/main/java/com/loopeer/codereader/model/DirectoryNode.java b/app/src/main/java/com/loopeer/codereader/model/DirectoryNode.java deleted file mode 100644 index af9342f..0000000 --- a/app/src/main/java/com/loopeer/codereader/model/DirectoryNode.java +++ /dev/null @@ -1,40 +0,0 @@ -package com.loopeer.codereader.model; - -import java.util.List; - -public class DirectoryNode extends BaseModel implements Comparable{ - - public String name; - public List pathNodes; - public boolean isDirectory; - public boolean openChild; - public int depth; - public String displayName; - public String absolutePath; - - public DirectoryNode() { - } - - public DirectoryNode(String name) { - this.name = name; - } - - public DirectoryNode(List pathNodes, String name) { - this.pathNodes = pathNodes; - this.name = name; - } - - @Override - public int compareTo(Object o) { - if (!(o instanceof DirectoryNode)) { - return 1; - } - if (((DirectoryNode) o).isDirectory && !isDirectory) { - return 1; - } - if (!((DirectoryNode) o).isDirectory && isDirectory) { - return -1; - } - return name.compareTo(((DirectoryNode) o).name); - } -} diff --git a/app/src/main/java/com/loopeer/codereader/model/MainHeaderItem.java b/app/src/main/java/com/loopeer/codereader/model/MainHeaderItem.java deleted file mode 100644 index 6c2e661..0000000 --- a/app/src/main/java/com/loopeer/codereader/model/MainHeaderItem.java +++ /dev/null @@ -1,14 +0,0 @@ -package com.loopeer.codereader.model; - -public class MainHeaderItem { - - public int icon; - public int name; - public String link; - - public MainHeaderItem(int icon, int name, String link) { - this.icon = icon; - this.name = name; - this.link = link; - } -} diff --git a/app/src/main/java/com/loopeer/codereader/model/Repo.java b/app/src/main/java/com/loopeer/codereader/model/Repo.java deleted file mode 100644 index b83e2a5..0000000 --- a/app/src/main/java/com/loopeer/codereader/model/Repo.java +++ /dev/null @@ -1,95 +0,0 @@ -package com.loopeer.codereader.model; - -import android.text.TextUtils; - -import com.loopeer.directorychooser.FileNod; - -public class Repo extends BaseModel{ - - public String name; - public long lastModify; - public String absolutePath; - public String netDownloadUrl; - public boolean isFolder; - public long downloadId; - public float factor; - public boolean isUnzip; - - public Repo() { - } - - public Repo(String name, String absolutePath, String netDownloadUrl, boolean isFolder, long downloadId) { - this.name = name; - this.absolutePath = absolutePath; - this.netDownloadUrl = netDownloadUrl; - this.isFolder = isFolder; - this.downloadId = downloadId; - } - - public boolean isDownloading() { - return downloadId > 0; - } - - public static Repo parse(FileNod node) { - Repo result = new Repo(); - result.name = node.name; - result.absolutePath = node.absolutePath; - result.isFolder = node.isFolder; - return result; - } - - public DirectoryNode toDirectoryNode() { - DirectoryNode node = new DirectoryNode(); - node.name = name; - node.absolutePath = absolutePath; - node.isDirectory = isFolder; - return node; - } - - public boolean isNetRepo() { - return !TextUtils.isEmpty(netDownloadUrl); - } - - public boolean isLocalRepo() { - return !TextUtils.isEmpty(absolutePath); - } - - @Override - public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; - - Repo repo = (Repo) o; - - if (id != null ? !id.equals(repo.id) : repo.id != null) return false; - if (name != null ? !name.equals(repo.name) : repo.name != null) return false; - if (absolutePath != null ? !absolutePath.equals(repo.absolutePath) : repo.absolutePath != null) - return false; - return netDownloadUrl != null ? netDownloadUrl.equals(repo.netDownloadUrl) : repo.netDownloadUrl == null; - - } - - @Override - public int hashCode() { - int result = name != null ? name.hashCode() : 0; - result = 31 * result + (id != null ? id.hashCode() : 0); - result = 31 * result + (absolutePath != null ? absolutePath.hashCode() : 0); - result = 31 * result + (netDownloadUrl != null ? netDownloadUrl.hashCode() : 0); - return result; - } - - @Override - public String toString() { - return "Repo{" + - "id='" + id + '\'' + - "name='" + name + '\'' + - ", lastModify=" + lastModify + - ", absolutePath='" + absolutePath + '\'' + - ", netDownloadUrl='" + netDownloadUrl + '\'' + - ", isFolder=" + isFolder + - ", downloadId=" + downloadId + - ", factor=" + factor + - ", isUnzip=" + isUnzip + - '}'; - } -} diff --git a/app/src/main/java/com/loopeer/codereader/model/Repository.java b/app/src/main/java/com/loopeer/codereader/model/Repository.java deleted file mode 100644 index 0cd3140..0000000 --- a/app/src/main/java/com/loopeer/codereader/model/Repository.java +++ /dev/null @@ -1,67 +0,0 @@ -package com.loopeer.codereader.model; - -import com.google.gson.annotations.SerializedName; - -public class Repository extends BaseModel { - - @SerializedName("name") - public String name; - @SerializedName("full_name") - public String fullName; - @SerializedName("owner") - public Owner owner; - @SerializedName("private") - public boolean privateX; - @SerializedName("html_url") - public String htmlUrl; - @SerializedName("description") - public String description; - @SerializedName("fork") - public boolean fork; - @SerializedName("url") - public String url; - @SerializedName("created_at") - public String createdAt; - @SerializedName("updated_at") - public String updatedAt; - @SerializedName("pushed_at") - public String pushedAt; - @SerializedName("homepage") - public String homepage; - @SerializedName("size") - public int size; - @SerializedName("stargazers_count") - public int stargazersCount; - @SerializedName("watchers_count") - public int watchersCount; - @SerializedName("language") - public String language; - @SerializedName("forks_count") - public int forksCount; - @SerializedName("open_issues_count") - public int openIssuesCount; - @SerializedName("master_branch") - public String masterBranch; - @SerializedName("default_branch") - public String defaultBranch; - @SerializedName("score") - public double score; - - public static class Owner { - @SerializedName("login") - public String login; - @SerializedName("id") - public int id; - @SerializedName("avatar_url") - public String avatarUrl; - @SerializedName("gravatar_id") - public String gravatarId; - @SerializedName("url") - public String url; - @SerializedName("received_events_url") - public String receivedEventsUrl; - @SerializedName("type") - public String type; - } - -} diff --git a/app/src/main/java/com/loopeer/codereader/model/TargetFile.java b/app/src/main/java/com/loopeer/codereader/model/TargetFile.java deleted file mode 100644 index c76b25e..0000000 --- a/app/src/main/java/com/loopeer/codereader/model/TargetFile.java +++ /dev/null @@ -1,6 +0,0 @@ -package com.loopeer.codereader.model; - -public class TargetFile extends BaseModel { - - public String path; -} diff --git a/app/src/main/java/com/loopeer/codereader/sync/DownloadReceiver.java b/app/src/main/java/com/loopeer/codereader/sync/DownloadReceiver.java deleted file mode 100644 index a01aa4b..0000000 --- a/app/src/main/java/com/loopeer/codereader/sync/DownloadReceiver.java +++ /dev/null @@ -1,30 +0,0 @@ -package com.loopeer.codereader.sync; - -import android.app.DownloadManager; -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; - -import com.loopeer.codereader.Navigator; - -public class DownloadReceiver extends BroadcastReceiver { - - @Override - public void onReceive(Context context, Intent intent) { - if (intent.hasExtra(DownloadManager.EXTRA_DOWNLOAD_ID)) { - long downloadId = intent.getLongExtra( - DownloadManager.EXTRA_DOWNLOAD_ID, 0); - if (downloadId > 0) { - Intent i = new Intent(context, DownloadRepoService.class); - i.putExtra(DownloadManager.EXTRA_DOWNLOAD_ID, downloadId); - i.putExtra(Navigator.EXTRA_DOWNLOAD_SERVICE_TYPE, DownloadRepoService.DOWNLOAD_COMPLETE); - context.startService(i); - } - } - - if (intent.hasExtra(DownloadManager.EXTRA_NOTIFICATION_CLICK_DOWNLOAD_IDS)) { - Navigator.startMainActivity(context); - } - } - -} diff --git a/app/src/main/java/com/loopeer/codereader/sync/DownloadRepoService.java b/app/src/main/java/com/loopeer/codereader/sync/DownloadRepoService.java deleted file mode 100644 index 92cb2a3..0000000 --- a/app/src/main/java/com/loopeer/codereader/sync/DownloadRepoService.java +++ /dev/null @@ -1,272 +0,0 @@ -package com.loopeer.codereader.sync; - -import android.app.DownloadManager; -import android.app.Service; -import android.content.Context; -import android.content.Intent; -import android.database.ContentObserver; -import android.database.Cursor; -import android.net.Uri; -import android.os.Handler; -import android.os.IBinder; -import android.support.annotation.Nullable; -import android.util.Log; - -import com.loopeer.codereader.CodeReaderApplication; -import com.loopeer.codereader.Navigator; -import com.loopeer.codereader.R; -import com.loopeer.codereader.coreader.db.CoReaderDbHelper; -import com.loopeer.codereader.event.DownloadFailDeleteEvent; -import com.loopeer.codereader.event.DownloadProgressEvent; -import com.loopeer.codereader.event.DownloadRepoMessageEvent; -import com.loopeer.codereader.model.Repo; -import com.loopeer.codereader.utils.FileCache; -import com.loopeer.codereader.utils.RxBus; -import com.loopeer.codereader.utils.Unzip; - -import java.io.File; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; - -import rx.Observable; -import rx.Subscriber; -import rx.Subscription; -import rx.android.schedulers.AndroidSchedulers; -import rx.schedulers.Schedulers; - -public class DownloadRepoService extends Service { - public static final int DOWNLOAD_COMPLETE = 0; - public static final int DOWNLOAD_REPO = 1; - public static final int DOWNLOAD_PROGRESS = 2; - public static final int DOWNLOAD_REMOVE_DOWNLOAD = 3; - - private static final String TAG = "DownloadRepoService"; - public static final Uri DOWNLOAD_CONTENT_URI = Uri.parse("content://downloads/my_downloads"); - public static final String MEDIA_TYPE_ZIP = "application/zip"; - private HashMap mDownloadingRepos; - private Subscription mProgressSubscription; - private DownloadChangeObserver mDownloadChangeObserver; - - @Override - public void onCreate() { - super.onCreate(); - mDownloadingRepos = new HashMap<>(); - mDownloadChangeObserver = new DownloadChangeObserver(); - getContentResolver().registerContentObserver(DOWNLOAD_CONTENT_URI, true, - mDownloadChangeObserver); - } - - @Override - public int onStartCommand(Intent intent, int flags, int startId) { - parseIntent(intent); - return super.onStartCommand(intent, flags, startId); - } - - private void parseIntent(Intent intent) { - Intent in = intent; - int type = in.getIntExtra(Navigator.EXTRA_DOWNLOAD_SERVICE_TYPE, 0); - Repo repo = (Repo) in.getSerializableExtra(Navigator.EXTRA_REPO); - long id = in.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, 0); - switch (type) { - case DOWNLOAD_COMPLETE: - doRepoDownloadComplete(id); - break; - case DOWNLOAD_REPO: - downloadFile(repo); - break; - case DOWNLOAD_PROGRESS: - checkDownloadProgress(); - break; - case DOWNLOAD_REMOVE_DOWNLOAD: - removeDownloadingRepo(id); - break; - } - } - - private void doRepoDownloadComplete(long id) { - - CoReaderDbHelper.getInstance(CodeReaderApplication.getAppContext()) - .updateRepoUnzipProgress(id, 1, true); - RxBus.getInstance().send(new DownloadProgressEvent(id, true)); - Observable.create((Observable.OnSubscribe) subscriber -> { - Cursor cursor = null; - try { - DownloadManager manager = - (DownloadManager) DownloadRepoService.this.getSystemService(Context.DOWNLOAD_SERVICE); - DownloadManager.Query baseQuery = new DownloadManager.Query() - .setFilterById(id); - cursor = manager.query(baseQuery); - final int statusColumnId = cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_STATUS); - final int localFilenameColumnId = cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_LOCAL_FILENAME); - final int descName = cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_DESCRIPTION); - if (cursor.moveToNext()) { - final long status = cursor.getLong(statusColumnId); - final String path = cursor.getString(localFilenameColumnId); - final String name = cursor.getString(descName); - if (status == DownloadManager.STATUS_SUCCESSFUL) { - File zipFile = new File(path); - FileCache fileCache = FileCache.getInstance(); - fileCache.deleteFilesByDirectory(new File(fileCache.getCacheDir().getPath() + File.separator + name)); - Unzip decomp = new Unzip(zipFile.getPath() - , fileCache.getCacheDir().getPath() + File.separator + name, getApplicationContext()); - decomp.DecompressZip(); - if (zipFile.exists()) zipFile.delete(); - CoReaderDbHelper.getInstance(CodeReaderApplication.getAppContext()) - .updateRepoUnzipProgress(id, 1, false); - CoReaderDbHelper.getInstance( - CodeReaderApplication.getAppContext()).resetRepoDownloadId(mDownloadingRepos.get(id).downloadId); - RxBus.getInstance().send(new DownloadProgressEvent(id, false)); - RxBus.getInstance().send(new DownloadRepoMessageEvent( - getString(R.string.repo_download_complete, mDownloadingRepos.get(id).name))); - } else { - } - } - mDownloadingRepos.remove(id); - subscriber.onCompleted(); - } catch (Exception e) { - subscriber.onError(e); - } finally { - cursor.close(); - } - }) - .onErrorResumeNext(Observable.empty()) - .subscribeOn(Schedulers.io()) - .doOnError(e -> Log.d(TAG, e.toString())) - .doOnCompleted(this::checkTaskEmptyToFinish) - .subscribe(); - } - - private void checkTaskEmptyToFinish() { - if (mDownloadingRepos.isEmpty()) { - stopSelf(); - } - } - - private void downloadFile(Repo repo) { - RemoteRepoFetcher dataFetcher = new RemoteRepoFetcher(this, repo.netDownloadUrl, repo.name); - long downloadId = dataFetcher.download(); - if (downloadId <= 0) { - CoReaderDbHelper.getInstance(getApplicationContext()).deleteRepo(Long.parseLong(repo.id)); - return; - } - repo.downloadId = downloadId; - mDownloadingRepos.put(downloadId, repo); - CoReaderDbHelper.getInstance(getApplicationContext()).updateRepoDownloadId(downloadId, repo.id); - RxBus.getInstance().send(new DownloadRepoMessageEvent(getString(R.string.repo_download_start, repo.name))); - checkDownloadProgress(); - - } - - private void checkDownloadProgress() { - if (mDownloadingRepos.isEmpty()) { - List repos = CoReaderDbHelper.getInstance(this).readRepos(); - for (Repo repo : repos) { - if (repo.isDownloading()) { - mDownloadingRepos.put(repo.downloadId, repo); - } - } - } - if (mDownloadingRepos.isEmpty()) { - stopSelf(); - return; - } - if (mProgressSubscription != null && !mProgressSubscription.isUnsubscribed()) { - mProgressSubscription.unsubscribe(); - } - mProgressSubscription = checkDownloadingProgress(this); - } - - private void clearDownloadProgressSubscription() { - if (mProgressSubscription != null && !mProgressSubscription.isUnsubscribed()) { - mProgressSubscription.unsubscribe(); - } - } - - @Override - public void onDestroy() { - super.onDestroy(); - clearDownloadProgressSubscription(); - getContentResolver().unregisterContentObserver(mDownloadChangeObserver); - } - - public Subscription checkDownloadingProgress(Context context) { - return Observable.create(new Observable.OnSubscribe>() { - - @Override - public void call(Subscriber> subscriber) { - DownloadManager downloadManager = - (DownloadManager) context.getSystemService(Context.DOWNLOAD_SERVICE); - List repos = new ArrayList(mDownloadingRepos.values());; - for (Repo repo : repos) { - DownloadManager.Query q = new DownloadManager.Query(); - q.setFilterById(repo.downloadId); - - Cursor cursor = downloadManager.query(q); - cursor.moveToFirst(); - int bytes_downloaded = cursor.getInt(cursor - .getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR)); - int bytes_total = cursor.getInt( - cursor.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES)); - - String mediaType = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_MEDIA_TYPE)); - int reason = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_REASON)); - int status = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS)); - if (status == DownloadManager.STATUS_FAILED - && !MEDIA_TYPE_ZIP.equals(mediaType) - && reason == DownloadManager.ERROR_UNKNOWN) { - RxBus.getInstance() - .send(new DownloadRepoMessageEvent( - getString(R.string.repo_download_fail, repo.name))); - RxBus.getInstance() - .send(new DownloadFailDeleteEvent(repo)); - CoReaderDbHelper.getInstance(context).deleteRepo(Long.parseLong(repo.id)); - } else if (status != DownloadManager.STATUS_SUCCESSFUL) { - final float dl_progress = 1f * bytes_downloaded / bytes_total; - repo.factor = dl_progress; - } else if (status == DownloadManager.STATUS_SUCCESSFUL){ - repo.factor = 1; - } - if (repo.factor < 0) repo.factor = 0; - if (repo.factor >= 0) { - CoReaderDbHelper.getInstance(CodeReaderApplication.getAppContext()) - .updateRepoDownloadProgress(repo.downloadId, repo.factor); - RxBus.getInstance().send(new DownloadProgressEvent(repo.id, - repo.downloadId, repo.factor, repo.isUnzip)); - } - cursor.close(); - } - subscriber.onCompleted(); - } - }) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(); - } - - private void removeDownloadingRepo(long id) { - DownloadManager downloadManager = - (DownloadManager) this.getSystemService(Context.DOWNLOAD_SERVICE); - int i = downloadManager.remove(id); - if (i > 0) mDownloadingRepos.remove(id); - } - - @Nullable - @Override - public IBinder onBind(Intent intent) { - return null; - } - - class DownloadChangeObserver extends ContentObserver { - - public DownloadChangeObserver(){ - super(new Handler()); - } - - @Override - public void onChange(boolean selfChange) { - checkDownloadProgress(); - } - - } -} diff --git a/app/src/main/java/com/loopeer/codereader/sync/RemoteRepoFetcher.java b/app/src/main/java/com/loopeer/codereader/sync/RemoteRepoFetcher.java deleted file mode 100644 index cf3ebc3..0000000 --- a/app/src/main/java/com/loopeer/codereader/sync/RemoteRepoFetcher.java +++ /dev/null @@ -1,45 +0,0 @@ - -package com.loopeer.codereader.sync; - -import android.app.DownloadManager; -import android.content.Context; -import android.net.Uri; - -import com.loopeer.codereader.R; -import com.loopeer.codereader.event.DownloadRepoMessageEvent; -import com.loopeer.codereader.utils.DownloadUrlParser; -import com.loopeer.codereader.utils.RxBus; - -public class RemoteRepoFetcher { - private Context mContext; - private String mUrl; - private Uri mDestinationUri; - private String mRepoName; - - public RemoteRepoFetcher(Context context, String url, String repoName) { - mContext = context; - mUrl = url; - mRepoName = repoName; - mDestinationUri = Uri.fromFile(DownloadUrlParser.getRemoteRepoZipFileName(repoName)); - } - - public long download() { - DownloadManager manager = (DownloadManager) mContext.getSystemService(Context.DOWNLOAD_SERVICE); - Uri downloadUri = Uri.parse(mUrl); - DownloadManager.Request request = null; - try { - request = new DownloadManager.Request(downloadUri); - } catch (IllegalArgumentException e) { - RxBus.getInstance().send(new DownloadRepoMessageEvent(mContext.getString(R.string.repo_download_url_parse_error))); - return -1; - } - request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE); - request.setVisibleInDownloadsUi(false); - request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_MOBILE| DownloadManager.Request.NETWORK_WIFI); - request.setDescription(mRepoName); - request.setDestinationUri(mDestinationUri); - long downloadId = manager.enqueue(request); - return downloadId; - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/loopeer/codereader/ui/activity/AboutActivity.java b/app/src/main/java/com/loopeer/codereader/ui/activity/AboutActivity.java deleted file mode 100644 index 2cc580f..0000000 --- a/app/src/main/java/com/loopeer/codereader/ui/activity/AboutActivity.java +++ /dev/null @@ -1,87 +0,0 @@ -package com.loopeer.codereader.ui.activity; - -import android.content.Context; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; -import android.os.Bundle; -import android.text.SpannableString; -import android.text.Spanned; -import android.text.method.LinkMovementMethod; -import android.view.View; -import android.widget.TextView; - -import com.loopeer.codereader.Navigator; -import com.loopeer.codereader.R; -import com.loopeer.codereader.utils.CustomTextUtils; -import com.loopeer.directorychooser.ColorClickableSpan; - -import butterknife.BindView; - -public class AboutActivity extends BaseActivity { - - @BindView(R.id.text_about_version) - TextView mTextAboutVersion; - @BindView(R.id.text_about_content) - TextView mTextAboutContent; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_about); - setUpVersion(this); - setUpTextSpan(); - } - - private void setUpTextSpan() { - String aboutContent = getResources().getString(R.string.about_content); - int[] indexSource = CustomTextUtils.calculateTextStartEnd(aboutContent, getResources().getString(R.string.about_coreader)); - SpannableString aboutContentSpan = new SpannableString(aboutContent); - aboutContentSpan.setSpan(new ColorClickableSpan(this, R.color.colorPrimary) { - @Override - public void onClick(View widget) { - Navigator.startWebActivity(AboutActivity.this, getString(R.string.about_coreader_github_url)); - } - }, indexSource[0], indexSource[1], Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - - int[] indexEmail = CustomTextUtils.calculateTextStartEnd(aboutContent, - getResources().getString(R.string.about_email)); - aboutContentSpan.setSpan(new ColorClickableSpan(this, R.color.colorPrimary) { - @Override - public void onClick(View widget) { - Navigator.startComposeEmail(AboutActivity.this, - new String[]{getString(R.string.about_email)}, - getString(R.string.app_name), - getString(R.string.about_email_content_tip)); - } - }, indexEmail[0], indexEmail[1], Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - int[] indexFirDownload = CustomTextUtils.calculateTextStartEnd(aboutContent, - getResources().getString(R.string.about_fir_download)); - aboutContentSpan.setSpan(new ColorClickableSpan(this, R.color.colorPrimary) { - @Override - public void onClick(View widget) { - Navigator.startOutWebActivity(AboutActivity.this, getString(R.string.about_fir_download_url)); - } - }, indexFirDownload[0], indexFirDownload[1], Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - mTextAboutContent.setText(aboutContentSpan); - mTextAboutContent.setMovementMethod(LinkMovementMethod.getInstance()); - } - - private void setUpVersion(Context context) { - PackageManager packageManager = context.getPackageManager(); - PackageInfo packInfo = null; - try { - packInfo = packageManager.getPackageInfo(context.getPackageName(), 0); - } catch (PackageManager.NameNotFoundException e) { - e.printStackTrace(); - } - - String version = ""; - String code = ""; - if (packInfo != null) { - version = packInfo.versionName; - code = Integer.valueOf(packInfo.versionCode).toString(); - } - - mTextAboutVersion.setText("V" + version + "-" + code); - } -} diff --git a/app/src/main/java/com/loopeer/codereader/ui/activity/AddRepoActivity.java b/app/src/main/java/com/loopeer/codereader/ui/activity/AddRepoActivity.java deleted file mode 100644 index 00a1919..0000000 --- a/app/src/main/java/com/loopeer/codereader/ui/activity/AddRepoActivity.java +++ /dev/null @@ -1,72 +0,0 @@ -package com.loopeer.codereader.ui.activity; - -import android.os.Bundle; -import android.text.Editable; -import android.widget.Button; -import android.widget.EditText; - -import com.loopeer.codereader.Navigator; -import com.loopeer.codereader.R; -import com.loopeer.codereader.model.Repo; -import com.loopeer.codereader.ui.view.AddRepoChecker; -import com.loopeer.codereader.ui.view.Checker; -import com.loopeer.codereader.ui.view.TextWatcherImpl; -import com.loopeer.codereader.utils.FileCache; - -import butterknife.BindView; -import butterknife.OnClick; - -public class AddRepoActivity extends BaseActivity implements Checker.CheckObserver { - - @BindView(R.id.edit_add_repo_name) - EditText mEditAddRepoName; - @BindView(R.id.edit_add_repo_url) - EditText mEditAddRepoUrl; - @BindView(R.id.btn_add_repo) - Button mBtnAddRepo; - - private AddRepoChecker mAddRepoChecker; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_add_repo); - - mAddRepoChecker = new AddRepoChecker(this); - mEditAddRepoName.addTextChangedListener(new TextWatcherImpl() { - @Override - public void afterTextChanged(Editable editable) { - super.afterTextChanged(editable); - mAddRepoChecker.setRepoName(editable.toString()); - } - }); - mEditAddRepoUrl.addTextChangedListener(new TextWatcherImpl() { - @Override - public void afterTextChanged(Editable editable) { - super.afterTextChanged(editable); - mAddRepoChecker.setRepoDownloadUrl(editable.toString()); - } - }); - } - - - @Override - public void check(boolean b) { - mBtnAddRepo.setEnabled(b); - } - - @OnClick(R.id.btn_add_repo) - @SuppressWarnings("unused") - public void onClick() { - hideSoftInputMethod(); - Repo repo = new Repo( - mAddRepoChecker.repoName.trim() - , FileCache.getInstance().getRepoAbsolutePath(mAddRepoChecker.repoName) - , mAddRepoChecker.repoDownloadUrl.trim() - , true - , 0); - Navigator.startDownloadNewRepoService(this, repo); -// this.finish(); - } -} - diff --git a/app/src/main/java/com/loopeer/codereader/ui/activity/BaseActivity.java b/app/src/main/java/com/loopeer/codereader/ui/activity/BaseActivity.java deleted file mode 100644 index dd74f4a..0000000 --- a/app/src/main/java/com/loopeer/codereader/ui/activity/BaseActivity.java +++ /dev/null @@ -1,196 +0,0 @@ -package com.loopeer.codereader.ui.activity; - -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.os.Bundle; -import android.support.annotation.Nullable; -import android.support.design.widget.CoordinatorLayout; -import android.support.design.widget.Snackbar; -import android.support.v7.app.ActionBar; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.Toolbar; -import android.text.TextUtils; -import android.view.MenuItem; -import android.view.inputmethod.InputMethodManager; - -import com.loopeer.codereader.R; -import com.loopeer.codereader.event.DownloadRepoMessageEvent; -import com.loopeer.codereader.event.ThemeRecreateEvent; -import com.loopeer.codereader.ui.view.ProgressLoading; -import com.loopeer.codereader.utils.RxBus; - -import butterknife.BindView; -import butterknife.ButterKnife; -import rx.Subscription; -import rx.android.schedulers.AndroidSchedulers; -import rx.subscriptions.CompositeSubscription; - -public class BaseActivity extends AppCompatActivity { - - @Nullable - @BindView(R.id.toolbar) - protected Toolbar mToolbar; - @Nullable - @BindView(R.id.view_coordinator_container) - CoordinatorLayout mCoordinatorContainer; - - private final CompositeSubscription mAllSubscription = new CompositeSubscription(); - - @Override - protected void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - registerSubscription( - RxBus.getInstance() - .toObservable() - .filter(o -> o instanceof DownloadRepoMessageEvent) - .map(o -> (DownloadRepoMessageEvent) o) - .observeOn(AndroidSchedulers.mainThread()) - .doOnNext(o -> showMessage(o.getMessage())) - .subscribe()); - - registerSubscription( - RxBus.getInstance() - .toObservable() - .filter(o -> o instanceof ThemeRecreateEvent) - .observeOn(AndroidSchedulers.mainThread()) - .doOnNext(o -> recreate()) - .subscribe()); - } - - @Override - public void onContentChanged() { - super.onContentChanged(); - ButterKnife.bind(this); - - if (mToolbar != null) { - setSupportActionBar(mToolbar); - onSetupActionBar(getSupportActionBar()); - } - - String title = getIntent().getStringExtra(Intent.EXTRA_TITLE); - if (!TextUtils.isEmpty(title)) { - setTitle(title); - } - } - - protected void onSetupActionBar(ActionBar actionBar) { - actionBar.setDisplayHomeAsUpEnabled(true); - } - - protected void registerSubscription(Subscription subscription) { - mAllSubscription.add(subscription); - } - - protected void unregisterSubscription(Subscription subscription) { - mAllSubscription.remove(subscription); - } - - protected void clearSubscription() { - mAllSubscription.clear(); - } - - protected void reCreateRefresh() { - - } - - @Override - protected void onDestroy() { - super.onDestroy(); - clearSubscription(); - if (isProgressShow() && mProgressLoading != null) { - dismissProgressLoading(); - mProgressLoading = null; - } - } - - private ProgressLoading mProgressLoading; - private ProgressLoading mUnBackProgressLoading; - private boolean progressShow; - - public void showProgressLoading(int resId) { - showProgressLoading(getString(resId)); - } - - public void showProgressLoading(String message) { - if (mProgressLoading == null) { - mProgressLoading = new ProgressLoading(this, R.style.ProgressLoadingTheme); - mProgressLoading.setCanceledOnTouchOutside(true); - mProgressLoading.setOnCancelListener(new DialogInterface.OnCancelListener() { - @Override - public void onCancel(DialogInterface dialog) { - progressShow = false; - } - }); - } - if (!TextUtils.isEmpty(message)) { - mProgressLoading.setMessage(message); - } else { - mProgressLoading.setMessage(null); - } - progressShow = true; - mProgressLoading.show(); - } - - public boolean isProgressShow() { - return progressShow; - } - - public void dismissProgressLoading() { - if (mProgressLoading != null && !isFinishing()) { - progressShow = false; - mProgressLoading.dismiss(); - } - } - - public void showUnBackProgressLoading(int resId) { - showUnBackProgressLoading(getString(resId)); - } - - // 按返回键不可撤销的 - public void showUnBackProgressLoading(String message) { - if (mUnBackProgressLoading == null) { - mUnBackProgressLoading = new ProgressLoading(this, R.style.ProgressLoadingTheme) { - @Override - public void onBackPressed() { - } - }; - } - if (!TextUtils.isEmpty(message)) { - mUnBackProgressLoading.setMessage(message); - } else { - mUnBackProgressLoading.setMessage(null); - } - mUnBackProgressLoading.show(); - } - - public void dismissUnBackProgressLoading() { - if (mUnBackProgressLoading != null && !isFinishing()) { - mUnBackProgressLoading.dismiss(); - } - } - - public void hideSoftInputMethod() { - InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); - if(imm.isActive()){ - imm.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), 0); - } - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - if (item.getItemId() == android.R.id.home) { - finish(); - return true; - } - return super.onOptionsItemSelected(item); - } - - protected void showMessage(String message) { - if (mCoordinatorContainer != null) - Snackbar.make(mCoordinatorContainer, message, Snackbar.LENGTH_SHORT) - .setAction(R.string.snackbar_action, view -> {}) - .show(); - } -} diff --git a/app/src/main/java/com/loopeer/codereader/ui/activity/CodeReadActivity.java b/app/src/main/java/com/loopeer/codereader/ui/activity/CodeReadActivity.java deleted file mode 100644 index 5695dde..0000000 --- a/app/src/main/java/com/loopeer/codereader/ui/activity/CodeReadActivity.java +++ /dev/null @@ -1,158 +0,0 @@ -package com.loopeer.codereader.ui.activity; - -import android.content.Intent; -import android.os.Build; -import android.os.Bundle; -import android.support.v4.view.GravityCompat; -import android.support.v7.widget.RecyclerView; -import android.view.Menu; -import android.view.MenuItem; -import android.view.View; -import android.view.WindowManager; -import android.widget.FrameLayout; - -import com.loopeer.codereader.Navigator; -import com.loopeer.codereader.R; -import com.loopeer.codereader.coreader.db.CoReaderDbHelper; -import com.loopeer.codereader.model.DirectoryNode; -import com.loopeer.codereader.model.Repo; -import com.loopeer.codereader.ui.fragment.CodeReadFragment; -import com.loopeer.codereader.ui.view.DirectoryNavDelegate; -import com.loopeer.codereader.ui.view.DrawerLayout; -import com.loopeer.codereader.utils.DeviceUtils; - -import butterknife.BindView; - -public class CodeReadActivity extends BaseActivity implements DirectoryNavDelegate.FileClickListener, DirectoryNavDelegate.LoadFileCallback { - - @BindView(R.id.directory_view) - RecyclerView mDirectoryRecyclerView; - @BindView(R.id.left_sheet) - View mLeftSheet; - @BindView(R.id.container_code_read) - FrameLayout mContainer; - @BindView(R.id.drawer_layout) - DrawerLayout mDrawerLayout; - - private CodeReadFragment mFragment; - private DirectoryNode mDirectoryNode; - private DirectoryNode mSelectedNode; - - private DirectoryNavDelegate mDirectoryNavDelegate; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_code_read); - setupStatusBar(); - - mDirectoryNavDelegate = new DirectoryNavDelegate(mDirectoryRecyclerView, this); - mDirectoryNavDelegate.setLoadFileCallback(this); - createFragment(null); - parseIntent(savedInstanceState); - } - - private void setupStatusBar() { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) - return; - - getWindow().addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS); - mDirectoryRecyclerView.setPadding(0, DeviceUtils.getStatusBarHeight(), 0, 0); - mDirectoryRecyclerView.setClipToPadding(true); - } - - private void parseIntent(Bundle savedInstanceState) { - if (savedInstanceState != null) { - mDirectoryNode = (DirectoryNode) savedInstanceState.getSerializable(Navigator.EXTRA_DIRETORY_ROOT); - mSelectedNode = (DirectoryNode) savedInstanceState.getSerializable(Navigator.EXTRA_DIRETORY_SELECTING); - DirectoryNode rootNodeInstance = (DirectoryNode) savedInstanceState.getSerializable(Navigator.EXTRA_DIRETORY_ROOT_NODE_INSTANCE); - mFragment.updateRootNode(mDirectoryNode); - mDirectoryNavDelegate.resumeDirectoryState(rootNodeInstance); - doOpenFile(mSelectedNode); - return; - } - Intent intent = getIntent(); - Repo repo = (Repo) intent.getSerializableExtra(Navigator.EXTRA_REPO); - CoReaderDbHelper.getInstance(this).updateRepoLastModify(Long.valueOf(repo.id) - , System.currentTimeMillis()); - mDirectoryNode = repo.toDirectoryNode(); - mFragment.updateRootNode(mDirectoryNode); - mDirectoryNavDelegate.updateData(mDirectoryNode); - } - - @Override - protected void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - outState.putSerializable(Navigator.EXTRA_DIRETORY_ROOT, mDirectoryNode); - outState.putSerializable(Navigator.EXTRA_DIRETORY_SELECTING, mSelectedNode); - outState.putSerializable(Navigator.EXTRA_DIRETORY_ROOT_NODE_INSTANCE, mDirectoryNavDelegate.getDirectoryNodeInstance()); - } - - @Override - public void onBackPressed() { - if (mDrawerLayout.isDrawerOpen(GravityCompat.START)) { - mDrawerLayout.closeDrawer(GravityCompat.START); - } else { - super.onBackPressed(); - } - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.menu_code_read_go_out, menu); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - int id = item.getItemId(); - if (id == R.id.action_go_out) { - finish(); - return true; - } - if (id == android.R.id.home) { - if (!mDrawerLayout.isDrawerOpen(GravityCompat.START)) - mDrawerLayout.openDrawer(GravityCompat.START); - return true; - } - return super.onOptionsItemSelected(item); - } - - @Override - protected void onDestroy() { - super.onDestroy(); - mDirectoryNavDelegate.clearSubscription(); - mDirectoryNavDelegate = null; - } - - @Override - public void doOpenFile(DirectoryNode node) { - setTitle(node == null ? mDirectoryNode.name : node.name); - mSelectedNode = node; - loadCodeData(node); - } - - private void loadCodeData(DirectoryNode node) { - mDrawerLayout.closeDrawer(GravityCompat.START); - mFragment.openFile(node); - } - - private void createFragment(DirectoryNode node) { - mFragment = CodeReadFragment.newInstance(node, mDirectoryNode); - mFragment.setArguments(getIntent().getExtras()); - getSupportFragmentManager().beginTransaction() - .add(R.id.container_code_read, mFragment).commit(); - } - - @Override - public void onFileOpenStart() { - if (mFragment != null && mFragment.isVisible()) - mFragment.getCodeContentLoader().showProgress(); - } - - @Override - public void onFileOpenEnd() { - - } - -} diff --git a/app/src/main/java/com/loopeer/codereader/ui/activity/MainActivity.java b/app/src/main/java/com/loopeer/codereader/ui/activity/MainActivity.java deleted file mode 100644 index 0808453..0000000 --- a/app/src/main/java/com/loopeer/codereader/ui/activity/MainActivity.java +++ /dev/null @@ -1,204 +0,0 @@ -package com.loopeer.codereader.ui.activity; - -import android.Manifest; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.os.Bundle; -import android.support.annotation.Nullable; -import android.support.design.widget.FloatingActionButton; -import android.support.v4.app.ActivityCompat; -import android.support.v4.content.ContextCompat; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; -import android.view.Menu; -import android.view.MenuItem; -import android.widget.ViewAnimator; - -import com.loopeer.codereader.CodeReaderApplication; -import com.loopeer.codereader.Navigator; -import com.loopeer.codereader.R; -import com.loopeer.codereader.coreader.db.CoReaderDbHelper; -import com.loopeer.codereader.event.DownloadFailDeleteEvent; -import com.loopeer.codereader.model.Repo; -import com.loopeer.codereader.sync.DownloadRepoService; -import com.loopeer.codereader.ui.adapter.ItemTouchHelperCallback; -import com.loopeer.codereader.ui.adapter.MainLatestAdapter; -import com.loopeer.codereader.ui.decoration.DividerItemDecoration; -import com.loopeer.codereader.ui.decoration.DividerItemDecorationMainList; -import com.loopeer.codereader.ui.loader.ILoadHelper; -import com.loopeer.codereader.ui.loader.RecyclerLoader; -import com.loopeer.codereader.utils.RxBus; -import com.loopeer.directorychooser.FileNod; -import com.loopeer.directorychooser.NavigatorChooser; -import com.loopeer.itemtouchhelperextension.ItemTouchHelperExtension; - -import java.util.List; - -import butterknife.BindView; -import butterknife.OnClick; -import rx.android.schedulers.AndroidSchedulers; - -public class MainActivity extends BaseActivity { - private static final String TAG = "MainActivity"; - - public static final int MY_PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE = 1000; - @BindView(R.id.view_recycler) - RecyclerView mRecyclerView; - @BindView(R.id.animator_recycler_content) - ViewAnimator mAnimatorRecyclerContent; - @BindView(R.id.fab_main) - FloatingActionButton mFabMain; - - private ILoadHelper mRecyclerLoader; - private MainLatestAdapter mMainLatestAdapter; - - public ItemTouchHelperExtension mItemTouchHelper; - public ItemTouchHelperExtension.Callback mCallback; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_main); - getSupportActionBar().setDisplayHomeAsUpEnabled(false); - Navigator.startDownloadRepoService(this, DownloadRepoService.DOWNLOAD_PROGRESS); - if (ContextCompat.checkSelfPermission(this, - Manifest.permission.WRITE_EXTERNAL_STORAGE) - != PackageManager.PERMISSION_GRANTED) { - if (ActivityCompat.shouldShowRequestPermissionRationale(this, - Manifest.permission.WRITE_EXTERNAL_STORAGE)) { - } else { - ActivityCompat.requestPermissions(this, - new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, - MY_PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE); - } - } - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.menu_repo_add, menu); - getMenuInflater().inflate(R.menu.menu_settings, menu); - return super.onCreateOptionsMenu(menu); - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - switch (item.getItemId()) { - case R.id.action_settings: - Navigator.startSettingActivity(this); - break; - case R.id.action_repo_add: - Navigator.startAddRepoActivity(this); - break; - } - return super.onOptionsItemSelected(item); - } - - @Override - protected void onPostCreate(@Nullable Bundle savedInstanceState) { - super.onPostCreate(savedInstanceState); - setUpView(); - registerSubscription( - RxBus.getInstance().toObservable() - .filter(o -> o instanceof DownloadFailDeleteEvent) - .map(o -> ((DownloadFailDeleteEvent) o).deleteRepo) - .observeOn(AndroidSchedulers.mainThread()) - .doOnNext(mMainLatestAdapter::deleteRepo) - .subscribe() - ); - } - - @Override - protected void onResume() { - super.onResume(); - mRecyclerLoader.showProgress(); - loadLocalData(); - } - - private void setUpView() { - mRecyclerLoader = new RecyclerLoader(mAnimatorRecyclerContent); - mRecyclerView.setLayoutManager(new LinearLayoutManager(this)); - mMainLatestAdapter = new MainLatestAdapter(this); - mRecyclerView.setAdapter(mMainLatestAdapter); - mRecyclerView.addItemDecoration(new DividerItemDecorationMainList(this, - DividerItemDecoration.VERTICAL_LIST - , getResources().getDimensionPixelSize(R.dimen.repo_list_divider_start) - , -1 - , -1)); - mItemTouchHelper = createItemTouchHelper(); - mItemTouchHelper.attachToRecyclerView(mRecyclerView); - } - - public ItemTouchHelperExtension createItemTouchHelper() { - mCallback = createCallback(); - return new ItemTouchHelperExtension(mCallback); - } - - public ItemTouchHelperExtension.Callback createCallback() { - return new ItemTouchHelperCallback(); - } - - private void loadLocalData() { - List repos = - CoReaderDbHelper.getInstance(CodeReaderApplication.getAppContext()).readRepos(); - setUpContent(repos); - } - - @Override - protected void reCreateRefresh() { - super.reCreateRefresh(); - mRecyclerView.getRecycledViewPool().clear(); - mMainLatestAdapter.notifyDataSetChanged(); - } - - private void setUpContent(List repos) { - mRecyclerLoader.showContent(); - mMainLatestAdapter.updateData(repos); - } - - @OnClick(R.id.fab_main) - @SuppressWarnings("unused") - public void onClick() { - doSelectFile(); - } - - private void doSelectFile() { - NavigatorChooser.startDirectoryFileChooserActivity(this); - } - - @Override - public void onActivityResult(int requestCode, int resultCode, Intent data) { - super.onActivityResult(requestCode, resultCode, data); - switch (requestCode) { - case NavigatorChooser.DIRECTORY_FILE_SELECT_CODE: - if (resultCode == RESULT_OK) { - FileNod node = (FileNod) data.getSerializableExtra(NavigatorChooser.EXTRA_FILE_NODE); - Repo repo = Repo.parse(node); - repo.id = String.valueOf(CoReaderDbHelper.getInstance(this).insertRepo(repo)); - Navigator.startCodeReadActivity(MainActivity.this, repo); - } - break; - } - } - - @Override - protected void onPause() { - super.onPause(); - mMainLatestAdapter.clearSubscription(); - } - - @Override - public void onRequestPermissionsResult(int requestCode, - String permissions[], int[] grantResults) { - switch (requestCode) { - case MY_PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE: { - if (grantResults.length > 0 - && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - } else { - } - return; - } - } - } - -} diff --git a/app/src/main/java/com/loopeer/codereader/ui/activity/RepositoryFragment.java b/app/src/main/java/com/loopeer/codereader/ui/activity/RepositoryFragment.java deleted file mode 100644 index a4518fd..0000000 --- a/app/src/main/java/com/loopeer/codereader/ui/activity/RepositoryFragment.java +++ /dev/null @@ -1,137 +0,0 @@ -package com.loopeer.codereader.ui.activity; - -import android.os.Bundle; -import android.support.annotation.Nullable; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; - -import com.loopeer.codereader.R; -import com.loopeer.codereader.api.ServiceFactory; -import com.loopeer.codereader.api.service.GithubService; -import com.loopeer.codereader.model.Repository; -import com.loopeer.codereader.ui.adapter.RepositoryAdapter; -import com.loopeer.codereader.ui.decoration.DividerItemDecoration; -import com.loopeer.codereader.ui.fragment.BaseFragment; -import com.loopeer.codereader.utils.PageLinkParser; - -import java.util.ArrayList; -import java.util.List; - -import butterknife.BindView; -import rx.android.schedulers.AndroidSchedulers; -import rx.schedulers.Schedulers; - -public class RepositoryFragment extends BaseFragment { - - private static final int PAGE_SIZE = 10; - - @BindView(R.id.view_recycler) - RecyclerView mViewRecycler; - - private RepositoryAdapter mRepositoryAdapter; - private GithubService mGithubService; - - private String mSearchText; - - private List mRepositories = new ArrayList<>(); - - private PageLinkParser mPageLinkParser; - - private boolean mIsLoading; - - @Override - public void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - mRepositoryAdapter = new RepositoryAdapter(getActivity()); - mGithubService = ServiceFactory.getGithubService(); - } - - @Nullable - @Override - public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { - return inflater.inflate(R.layout.activity_search_result, container, false); - } - - @Override - public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - setupRecyclerView(); - } - - private void setupRecyclerView() { - mViewRecycler.setLayoutManager(new LinearLayoutManager(getActivity())); - mViewRecycler.setAdapter(mRepositoryAdapter); - mViewRecycler.addItemDecoration(new DividerItemDecoration(getContext())); - - mViewRecycler.addOnScrollListener(new RecyclerView.OnScrollListener() { - @Override - public void onScrollStateChanged(RecyclerView recyclerView, int newState) { - super.onScrollStateChanged(recyclerView, newState); - LinearLayoutManager layoutManager = (LinearLayoutManager) recyclerView.getLayoutManager(); - if (layoutManager.findLastVisibleItemPosition() == mRepositoryAdapter.getItemCount() - 1 && isHasMore()) { - if (!mIsLoading) - requestData(mPageLinkParser.getNext()); - } - } - }); - } - - public void setSearchText(String searchText) { - mSearchText = searchText; - requestData(1); - } - - public boolean isHasMore() { - if (mPageLinkParser != null && mPageLinkParser.getNext() != 0 && mPageLinkParser.getRemain() != 0) - return true; - return false; - } - - private void requestData(int page) { - mIsLoading = true; - - if (page == 1) { - mRepositories.clear(); - showProgressLoading(""); - } - - registerSubscription(mGithubService.repositories(mSearchText, null, null, page, PAGE_SIZE) - .filter(baseListResponseResponse -> baseListResponseResponse.isSuccessful()) - .map(baseListResponseResponse -> { - mPageLinkParser = new PageLinkParser(baseListResponseResponse); - return baseListResponseResponse.body().items; - }) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(repositories -> { - mRepositories.addAll(repositories); - mRepositoryAdapter.setHasMore(isHasMore()); - mRepositoryAdapter.updateData(mRepositories); - dismissProgressLoading(); - - mIsLoading = false; - }, throwable -> { - throwable.printStackTrace(); - dismissProgressLoading(); - - mIsLoading = false; - })); - } - - @Override - public void showProgressLoading(String message) { - mViewRecycler.setVisibility(View.INVISIBLE); - super.showProgressLoading(message); - getProgressLoading().setCanceledOnTouchOutside(false); - } - - @Override - public void dismissProgressLoading() { - mViewRecycler.setVisibility(View.VISIBLE); - super.dismissProgressLoading(); - } - -} diff --git a/app/src/main/java/com/loopeer/codereader/ui/activity/SearchActivity.java b/app/src/main/java/com/loopeer/codereader/ui/activity/SearchActivity.java deleted file mode 100644 index 9aed36b..0000000 --- a/app/src/main/java/com/loopeer/codereader/ui/activity/SearchActivity.java +++ /dev/null @@ -1,61 +0,0 @@ -package com.loopeer.codereader.ui.activity; - -import android.app.SearchManager; -import android.content.Context; -import android.os.Bundle; -import android.support.annotation.Nullable; -import android.support.v7.widget.SearchView; -import android.text.TextUtils; -import android.view.Menu; -import android.view.MenuItem; - -import com.loopeer.codereader.R; - -public class SearchActivity extends BaseActivity implements SearchView.OnQueryTextListener { - - private RepositoryFragment mRepositoryFragment; - private SearchView mSearchView; - - @Override - protected void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_search); - mRepositoryFragment = (RepositoryFragment) getSupportFragmentManager() - .findFragmentById(R.id.fragment_repository); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.menu_file_search, menu); - SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE); - mSearchView = (SearchView) menu.findItem(R.id.action_search).getActionView(); - mSearchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName())); - mSearchView.onActionViewExpanded(); - mSearchView.setMaxWidth(Integer.MAX_VALUE); - mSearchView.setOnQueryTextListener(this); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - if (item.getItemId() == android.R.id.home) { - this.finish(); - return true; - } - return super.onOptionsItemSelected(item); - } - - @Override - public boolean onQueryTextSubmit(String query) { - if (!TextUtils.isEmpty(query) && mRepositoryFragment != null) { - mRepositoryFragment.setSearchText(query); - mSearchView.clearFocus(); - } - return true; - } - - @Override - public boolean onQueryTextChange(String newText) { - return false; - } -} diff --git a/app/src/main/java/com/loopeer/codereader/ui/activity/SettingActivity.java b/app/src/main/java/com/loopeer/codereader/ui/activity/SettingActivity.java deleted file mode 100644 index 9ffcede..0000000 --- a/app/src/main/java/com/loopeer/codereader/ui/activity/SettingActivity.java +++ /dev/null @@ -1,149 +0,0 @@ -package com.loopeer.codereader.ui.activity; - -import android.os.Bundle; -import android.support.v7.app.AppCompatDelegate; -import android.support.v7.widget.AppCompatCheckBox; -import android.support.v7.widget.AppCompatSeekBar; -import android.view.View; -import android.widget.ImageView; -import android.widget.SeekBar; -import android.widget.TextView; - -import com.loopeer.codereader.Navigator; -import com.loopeer.codereader.R; -import com.loopeer.codereader.event.ThemeRecreateEvent; -import com.loopeer.codereader.ui.view.ForegroundRelativeLayout; -import com.loopeer.codereader.ui.view.ThemeChooser; -import com.loopeer.codereader.utils.PrefUtils; -import com.loopeer.codereader.utils.RxBus; -import com.loopeer.codereader.utils.ThemeUtils; -import com.loopeer.directorychooser.ForegroundLinearLayout; - -import butterknife.BindView; -import butterknife.ButterKnife; -import butterknife.OnClick; - -public class SettingActivity extends BaseActivity implements SeekBar.OnSeekBarChangeListener, ThemeChooser.OnItemSelectListener { - - - - @BindView(R.id.item_setting_font_size) - ForegroundRelativeLayout mItemSettingFontSize; - @BindView(R.id.item_setting_line_number) - ForegroundLinearLayout mItemSettingLineNumber; - @BindView(R.id.item_setting_use_menlo) - ForegroundLinearLayout mItemSettingUseMenlo; - @BindView(R.id.item_setting_theme) - ForegroundLinearLayout mItemSettingTheme; - @BindView(R.id.checkbox_show_line_number) - AppCompatCheckBox mCheckboxShowLineNumber; - @BindView(R.id.checkbox_menlo_font) - AppCompatCheckBox mCheckboxMenloFont; - @BindView(R.id.text_setting_font_size_temp) - TextView mTextSettingFontSizeTemp; - @BindView(R.id.seekbar_setting_font_size) - AppCompatSeekBar mSeekbarSettingFontSize; - @BindView(R.id.text_setting_font_current) - TextView mTextSettingFontCurrent; - @BindView(R.id.view_setting_theme_day) - ImageView mViewSettingThemeDay; - @BindView(R.id.view_setting_theme_night) - ImageView mViewSettingThemeNight; - - private ThemeChooser mThemeChooser; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_setting); - ButterKnife.bind(this); - - mThemeChooser = new ThemeChooser(this, this); - mThemeChooser.addItem(mViewSettingThemeDay.getId(), ThemeUtils.THEME_DAY); - mThemeChooser.addItem(mViewSettingThemeNight.getId(), ThemeUtils.THEME_NIGHT); - initViewData(); - setUpView(); - } - - private void initViewData() { - mCheckboxShowLineNumber.setChecked(PrefUtils.getPrefDisplayLineNumber(this)); - mCheckboxMenloFont.setChecked(PrefUtils.getPrefMenlofont(this)); - int fontSize = (int) PrefUtils.getPrefFontSize(this); - mSeekbarSettingFontSize.setProgress(fontSize); - mTextSettingFontCurrent.setText(String.valueOf(fontSize)); - mTextSettingFontSizeTemp.setTextSize(fontSize); - mThemeChooser.onItemSelectByTag(PrefUtils.getPrefTheme(this)); - } - - private void setUpView() { - mCheckboxShowLineNumber.setOnCheckedChangeListener((compoundButton, b) - -> PrefUtils.setPrefDisplayLineNumber(SettingActivity.this, b)); - mCheckboxMenloFont.setOnCheckedChangeListener((compoundButton, b) - -> PrefUtils.setPrefMenlofont(SettingActivity.this, b)); - mSeekbarSettingFontSize.setOnSeekBarChangeListener(this); - } - - @OnClick({ - R.id.item_setting_font_size, - R.id.item_setting_line_number, - R.id.item_setting_use_menlo, - R.id.item_setting_theme, - R.id.view_setting_theme_day, - R.id.view_setting_theme_night, - R.id.item_setting_about - }) - @SuppressWarnings("unused") - public void onClick(View view) { - switch (view.getId()) { - case R.id.item_setting_font_size: - - break; - case R.id.item_setting_line_number: - mCheckboxShowLineNumber.setChecked(!mCheckboxShowLineNumber.isChecked()); - break; - case R.id.item_setting_use_menlo: - mCheckboxMenloFont.setChecked(!mCheckboxMenloFont.isChecked()); - break; - case R.id.item_setting_theme: - - break; - case R.id.view_setting_theme_day: - case R.id.view_setting_theme_night: - mThemeChooser.onItemSelect(view); - break; - case R.id.item_setting_about: - Navigator.startAboutActivity(this); - break; - } - } - - @Override - public void onProgressChanged(SeekBar seekBar, int i, boolean b) { - mTextSettingFontSizeTemp.setTextSize(i); - PrefUtils.setPrefFontSize(this, i); - mTextSettingFontCurrent.setText(String.valueOf(i)); - } - - @Override - public void onStartTrackingTouch(SeekBar seekBar) { - - } - - @Override - public void onStopTrackingTouch(SeekBar seekBar) { - - } - - @Override - public void onItemSelect(int id, String tag) { - if (PrefUtils.getPrefTheme(this).equals(tag)) { - return; - } - AppCompatDelegate.setDefaultNightMode(tag.equals(ThemeUtils.THEME_DAY) - ? AppCompatDelegate.MODE_NIGHT_NO - : AppCompatDelegate.MODE_NIGHT_YES); - - RxBus.getInstance().send(new ThemeRecreateEvent()); - PrefUtils.setPrefTheme(this, tag); - } -} diff --git a/app/src/main/java/com/loopeer/codereader/ui/activity/SimpleWebActivity.java b/app/src/main/java/com/loopeer/codereader/ui/activity/SimpleWebActivity.java deleted file mode 100644 index c8e95dd..0000000 --- a/app/src/main/java/com/loopeer/codereader/ui/activity/SimpleWebActivity.java +++ /dev/null @@ -1,194 +0,0 @@ -package com.loopeer.codereader.ui.activity; - -import android.annotation.TargetApi; -import android.app.SearchManager; -import android.content.Context; -import android.content.Intent; -import android.os.Build; -import android.os.Bundle; -import android.support.v4.view.MenuItemCompat; -import android.support.v7.widget.SearchView; -import android.support.v7.widget.Toolbar; -import android.text.TextUtils; -import android.view.KeyEvent; -import android.view.Menu; -import android.view.MenuItem; -import android.view.View; -import android.view.inputmethod.EditorInfo; -import android.webkit.WebResourceRequest; -import android.webkit.WebSettings; -import android.webkit.WebView; -import android.webkit.WebViewClient; -import android.widget.ProgressBar; - -import com.loopeer.codereader.Navigator; -import com.loopeer.codereader.R; -import com.loopeer.codereader.ui.view.NestedScrollWebView; -import com.loopeer.codereader.utils.DownloadUrlParser; - -import butterknife.BindView; -import butterknife.ButterKnife; - -public class SimpleWebActivity extends BaseActivity implements SearchView.OnQueryTextListener { - private static final String TAG = "SimpleWebActivity"; - - @BindView(R.id.web_content) - NestedScrollWebView mWebContent; - @BindView(R.id.toolbar) - Toolbar mToolbar; - @BindView(R.id.progress_bar_web) - ProgressBar mProgressBar; - private SearchView mSearchView; - - private String mUrl; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_simple_web); - ButterKnife.bind(this); - - initWeb(); - parseIntent(); - } - - private void initWeb() { - mWebContent.getSettings().setJavaScriptEnabled(true); - mWebContent.getSettings().setDomStorageEnabled(true); - mWebContent.getSettings().setGeolocationEnabled(true); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - mWebContent.getSettings().setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW); - } - - mWebContent.setWebViewClient(new WebViewClient() { - @Override - public boolean shouldOverrideUrlLoading(WebView view, String url) { - mSearchView.setQuery(url, true); - return true; - } - - @TargetApi(Build.VERSION_CODES.LOLLIPOP) - @Override - public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) { - mSearchView.setQuery(String.valueOf(request.getUrl()), true); - return true; - } - }); - mWebContent.setWebChromeClient(new WebChromeClient()); - } - - private void parseIntent() { - Intent intent = getIntent(); - mUrl = intent.getStringExtra(Navigator.EXTRA_WEB_URL); - String htmlString = intent.getStringExtra(Navigator.EXTRA_HTML_STRING); - if (mUrl == null) mUrl = intent.getDataString(); - if (htmlString != null) loadData(htmlString); - } - - private void loadData(String htmlString) { - mWebContent.loadData(htmlString, "text/html", "utf-8"); - } - - private void loadUrl(String webUrl) { - mWebContent.loadUrl(webUrl); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.menu_web_input, menu); - SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE); - MenuItem inputView = menu.findItem(R.id.action_web_input); - mSearchView= (SearchView) inputView.getActionView(); - mSearchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName())); - mSearchView.setIconified(false); - mSearchView.setOnQueryTextListener(this); - mSearchView.setImeOptions(EditorInfo.IME_ACTION_GO); - mSearchView.setQueryHint(getString(R.string.web_url_input_hint)); - mSearchView.setMaxWidth(Integer.MAX_VALUE); - if (mUrl != null && mSearchView != null) mSearchView.setQuery(mUrl, true); - MenuItemCompat.setOnActionExpandListener(inputView, new MenuItemCompat.OnActionExpandListener() { - @Override - public boolean onMenuItemActionExpand(MenuItem item) { - mSearchView.post(() -> mSearchView.setQuery(mUrl, false)); - return true; - } - - @Override - public boolean onMenuItemActionCollapse(MenuItem item) { - return true; - } - }); - getMenuInflater().inflate(R.menu.menu_web_save, menu); - getMenuInflater().inflate(R.menu.menu_web_actions, menu); - return true; - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - int id = item.getItemId(); - if (id == R.id.action_save) { - if (!TextUtils.isEmpty(mUrl) - && !DownloadUrlParser.parseGithubUrlAndDownload(SimpleWebActivity.this, mUrl)) { - showMessage(getString(R.string.repo_download_url_parse_error)); - } - return true; - } - if (id == R.id.menu_action_open_by_browser) { - Navigator.startOutWebActivity(this, mUrl); - return true; - } - return super.onOptionsItemSelected(item); - } - - @Override - public boolean onKeyDown(int keyCode, KeyEvent event) { - if (event.getAction() == KeyEvent.ACTION_DOWN) { - switch (keyCode) { - case KeyEvent.KEYCODE_BACK: - if (mWebContent.canGoBack()) { - mWebContent.goBack(); - } else { - finish(); - } - return true; - } - } - return super.onKeyDown(keyCode, event); - } - - @Override - public boolean onQueryTextSubmit(String query) { - if (!TextUtils.isEmpty(query)) { - mUrl = query; - loadUrl(mUrl); - mSearchView.clearFocus(); - } - return false; - } - - @Override - public boolean onQueryTextChange(String newText) { - return false; - } - - public class WebChromeClient extends android.webkit.WebChromeClient { - @Override - public void onProgressChanged(WebView view, int newProgress) { - if (newProgress == 100) { - mProgressBar.setVisibility(View.GONE); - } else { - if (mProgressBar.getVisibility() == View.GONE) - mProgressBar.setVisibility(View.VISIBLE); - mProgressBar.setProgress(newProgress); - } - super.onProgressChanged(view, newProgress); - } - - } - - @Override - protected void onDestroy() { - super.onDestroy(); - mWebContent.destroy(); - } -} \ No newline at end of file diff --git a/app/src/main/java/com/loopeer/codereader/ui/adapter/DirectoryAdapter.java b/app/src/main/java/com/loopeer/codereader/ui/adapter/DirectoryAdapter.java deleted file mode 100644 index 6cc78c5..0000000 --- a/app/src/main/java/com/loopeer/codereader/ui/adapter/DirectoryAdapter.java +++ /dev/null @@ -1,195 +0,0 @@ -package com.loopeer.codereader.ui.adapter; - -import android.content.Context; -import android.graphics.drawable.Drawable; -import android.support.v4.content.ContextCompat; -import android.support.v7.widget.RecyclerView; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.TextView; - -import com.loopeer.codereader.R; -import com.loopeer.codereader.model.DirectoryNode; -import com.loopeer.codereader.ui.view.DirectoryNavDelegate; - -import java.util.ArrayList; -import java.util.List; - -import butterknife.BindView; -import butterknife.ButterKnife; - -public class DirectoryAdapter extends RecyclerViewAdapter { - - DirectoryNode mNodeRoot; - private DirectoryNavDelegate.FileClickListener mFileClickListener; - - public DirectoryAdapter(Context context, DirectoryNavDelegate.FileClickListener fileClickListener) { - super(context); - mFileClickListener = fileClickListener; - } - - public void setNodeRoot(DirectoryNode root) { - mNodeRoot = root; - updateData(adaptNodes()); - } - - public DirectoryNode getNodeRoot() { - return mNodeRoot; - } - - private List adaptNodes() { - ArrayList nodes = new ArrayList<>(); - if (mNodeRoot.isDirectory) { - StringBuilder sb = new StringBuilder(); - createShowNodes(nodes, -1, mNodeRoot, sb); - } else { - mNodeRoot.displayName = mNodeRoot.name; - nodes.add(mNodeRoot); - } - return nodes; - } - - private void createShowNodes(ArrayList nodes, int i, DirectoryNode nodeRoot, StringBuilder sb) { - ++i; - if (!nodeRoot.pathNodes.isEmpty()) { - if (nodes.size() != 0 && nodeRoot.pathNodes.size() == 1 && nodeRoot.pathNodes.get(0).isDirectory) { - nodeRoot.openChild = true; - nodes.remove(nodes.size() - 1); - --i; - DirectoryNode node = nodeRoot.pathNodes.get(0); - node.depth = i; - sb.append("."); - sb.append(node.name); - node.displayName = sb.toString(); - nodes.add(node); - if (node.openChild || - (node.pathNodes != null - && node.pathNodes.size() == 1 - && node.pathNodes.get(0).isDirectory)) { - createShowNodes(nodes, i, node, sb); - } - } else { - for (DirectoryNode node : nodeRoot.pathNodes) { - if (sb.length() > 0) sb.delete(0, sb.length()); - node.depth = i; - sb.append(node.name); - node.displayName = sb.toString(); - nodes.add(node); - if (node.openChild || - (node.pathNodes != null - && node.pathNodes.size() == 1 - && node.pathNodes.get(0).isDirectory)) { - createShowNodes(nodes, i, node, sb); - } - } - } - } - } - - @Override - public void bindView(final DirectoryNode var1, int var2, RecyclerView.ViewHolder var3) { - if (var3 instanceof DirectoryViewHolder) { - DirectoryViewHolder viewHolder = (DirectoryViewHolder) var3; - viewHolder.bind(var1); - View.OnClickListener clickListener = view -> { - if (var1.isDirectory) { - var1.openChild = !var1.openChild; - updateData(adaptNodes()); - } else { - mFileClickListener.doOpenFile(var1); - } - }; - viewHolder.itemView.setOnClickListener(clickListener); - } - if (var3 instanceof CodeReadRepoHeaderViewHolder) { - CodeReadRepoHeaderViewHolder viewHolder = (CodeReadRepoHeaderViewHolder) var3; - viewHolder.bind(mNodeRoot); - } - - } - - @Override - public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { - LayoutInflater inflater = getLayoutInflater(); - View view; - switch (viewType) { - case R.layout.list_item_code_read_repo_header: - view = inflater.inflate(R.layout.list_item_code_read_repo_header, parent, false); - return new CodeReadRepoHeaderViewHolder(view); - default: - view = inflater.inflate(R.layout.list_item_directory, parent, false); - return new DirectoryViewHolder(view); - } - } - - @Override - public int getItemViewType(int position) { - if (position == 0) { - return R.layout.list_item_code_read_repo_header; - } - return R.layout.list_item_directory; - } - - @Override - public int getItemCount() { - return super.getItemCount() == 0 ? 0 : super.getItemCount() + 1; - } - - @Override - public DirectoryNode getItem(int position) { - if (position == 0) return mNodeRoot; - return super.getItem(position - 1); - } - - class DirectoryViewHolder extends RecyclerView.ViewHolder { - @BindView(R.id.text_directory_name) - TextView mTextDirectoryName; - @BindView(R.id.img_directory_open_close) - ImageView mImgOpenClose; - - DirectoryNode mNode; - - public DirectoryViewHolder(View itemView) { - super(itemView); - ButterKnife.bind(this, itemView); - } - - public void bind(DirectoryNode pathNode) { - mNode = pathNode; - mTextDirectoryName.setText(pathNode.displayName); - RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) itemView.getLayoutParams(); - params.leftMargin = 20 * pathNode.depth; - mImgOpenClose.setSelected(mNode.openChild); - int drawableId = R.drawable.ic_directory_file; - if (pathNode.isDirectory) { - drawableId = R.drawable.ic_directory_path; - mImgOpenClose.setVisibility(pathNode.pathNodes.isEmpty() ? View.INVISIBLE : View.VISIBLE); - } else { - mImgOpenClose.setVisibility(View.INVISIBLE); - } - Drawable drawable = ContextCompat.getDrawable(itemView.getContext(), drawableId); - mTextDirectoryName.setCompoundDrawablesWithIntrinsicBounds(drawable, null, null, null); - } - - } - - class CodeReadRepoHeaderViewHolder extends RecyclerView.ViewHolder { - - @BindView(R.id.img_code_read_repo_type) - ImageView mImgRepoType; - @BindView(R.id.text_code_read_repo_name) - TextView mTextRepoName; - - public CodeReadRepoHeaderViewHolder(View itemView) { - super(itemView); - ButterKnife.bind(this, itemView); - } - - public void bind(DirectoryNode directoryNode) { - mImgRepoType.setImageResource(directoryNode.isDirectory ? R.drawable.ic_repo_white : R.drawable.ic_document_white); - mTextRepoName.setText(directoryNode.name); - } - } -} diff --git a/app/src/main/java/com/loopeer/codereader/ui/adapter/ItemTouchHelperCallback.java b/app/src/main/java/com/loopeer/codereader/ui/adapter/ItemTouchHelperCallback.java deleted file mode 100644 index 18ea5da..0000000 --- a/app/src/main/java/com/loopeer/codereader/ui/adapter/ItemTouchHelperCallback.java +++ /dev/null @@ -1,31 +0,0 @@ -package com.loopeer.codereader.ui.adapter; - -import android.graphics.Canvas; -import android.support.v7.widget.RecyclerView; -import android.support.v7.widget.helper.ItemTouchHelper; - -import com.loopeer.itemtouchhelperextension.ItemTouchHelperExtension; - -public class ItemTouchHelperCallback extends ItemTouchHelperExtension.Callback { - - @Override - public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) { - return makeMovementFlags(0, ItemTouchHelper.START); - } - - @Override - public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) { - return false; - } - - @Override - public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) { - - } - - @Override - public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) { - if (viewHolder instanceof MainLatestAdapter.RepoViewHolder) - ((MainLatestAdapter.RepoViewHolder) viewHolder).mProgressRelativeLayout.setTranslationX(dX); - } -} diff --git a/app/src/main/java/com/loopeer/codereader/ui/adapter/MainHeaderAdapter.java b/app/src/main/java/com/loopeer/codereader/ui/adapter/MainHeaderAdapter.java deleted file mode 100644 index f303971..0000000 --- a/app/src/main/java/com/loopeer/codereader/ui/adapter/MainHeaderAdapter.java +++ /dev/null @@ -1,80 +0,0 @@ -package com.loopeer.codereader.ui.adapter; - -import android.content.Context; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.BaseAdapter; -import android.widget.ImageView; -import android.widget.TextView; - -import com.loopeer.codereader.Navigator; -import com.loopeer.codereader.R; -import com.loopeer.codereader.model.MainHeaderItem; - -import java.util.ArrayList; -import java.util.List; - -public class MainHeaderAdapter extends BaseAdapter { - - private Context mContext; - private List mDatas; - - public MainHeaderAdapter(Context context) { - mContext = context; - mDatas = new ArrayList<>(); - } - - @Override - public int getCount() { - return mDatas.size(); - } - - @Override - public Object getItem(int i) { - return mDatas.get(i); - } - - @Override - public long getItemId(int i) { - return i; - } - - @Override - public View getView(int i, View convertView, ViewGroup viewGroup) { - View view = LayoutInflater.from(mContext).inflate(R.layout.grid_item_main_header, viewGroup, false); - bindView(mDatas.get(i), view); - bindClick(view, mDatas.get(i), i); - return view; - } - - private void bindClick(View view, MainHeaderItem item, int i) { - view.setOnClickListener(view1 -> { - switch (i) { - case 0: - Navigator.startSearchActivity(mContext); - break; - case 1: - Navigator.startWebActivity(mContext, item.link); - break; - } - }); - } - - private void bindView(MainHeaderItem item, View view) { - TextView textView = (TextView) view.findViewById(R.id.text_grid_item); - ImageView imageView = (ImageView) view.findViewById(R.id.img_grid_item); - textView.setText(item.name); - imageView.setImageResource(item.icon); - } - - public void updateData(List items) { - setData(items); - notifyDataSetChanged(); - } - - public void setData(List items) { - mDatas.clear(); - mDatas.addAll(items); - } -} diff --git a/app/src/main/java/com/loopeer/codereader/ui/adapter/MainLatestAdapter.java b/app/src/main/java/com/loopeer/codereader/ui/adapter/MainLatestAdapter.java deleted file mode 100644 index 051a99f..0000000 --- a/app/src/main/java/com/loopeer/codereader/ui/adapter/MainLatestAdapter.java +++ /dev/null @@ -1,213 +0,0 @@ -package com.loopeer.codereader.ui.adapter; - -import android.content.Context; -import android.support.v7.widget.RecyclerView; -import android.text.format.DateUtils; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.GridView; -import android.widget.ImageView; -import android.widget.TextView; - -import com.loopeer.codereader.Navigator; -import com.loopeer.codereader.R; -import com.loopeer.codereader.coreader.db.CoReaderDbHelper; -import com.loopeer.codereader.event.DownloadProgressEvent; -import com.loopeer.codereader.model.MainHeaderItem; -import com.loopeer.codereader.model.Repo; -import com.loopeer.codereader.ui.view.ForegroundProgressRelativeLayout; -import com.loopeer.codereader.utils.RxBus; -import com.loopeer.itemtouchhelperextension.Extension; - -import java.util.ArrayList; -import java.util.List; - -import butterknife.BindView; -import butterknife.ButterKnife; -import rx.Subscription; -import rx.android.schedulers.AndroidSchedulers; -import rx.subscriptions.CompositeSubscription; - -public class MainLatestAdapter extends RecyclerViewAdapter { - private static final String TAG = "MainLatestAdapter"; - - public MainLatestAdapter(Context context) { - super(context); - } - - private final CompositeSubscription mAllSubscription = new CompositeSubscription(); - - @Override - public void setData(List data) { - ArrayList list = new ArrayList(); - list.add(null); - list.addAll(data); - super.setData(list); - } - - @Override - public void bindView(Repo var1, int var2, RecyclerView.ViewHolder var3) { - if (var3 instanceof RepoViewHolder) { - RepoViewHolder viewHolder = (RepoViewHolder) var3; - Subscription subscription = viewHolder.bind(var1); - if (subscription != null) { - mAllSubscription.add(subscription); - } - viewHolder.mProgressRelativeLayout.setOnClickListener(view -> { - if (!var1.isDownloading() && !var1.isUnzip) - Navigator.startCodeReadActivity(getContext(), var1); - }); - viewHolder.mActionDeleteView.setOnClickListener(view -> doRepoDelete(var3)); - viewHolder.mActionSyncView.setOnClickListener(view -> - Navigator.startDownloadRepoService(getContext(), var1) - ); - } - if (var3 instanceof MainHeaderHolder) { - MainHeaderHolder viewHolder = (MainHeaderHolder) var3; - viewHolder.bind(); - } - - } - - private void doRepoDelete(RecyclerView.ViewHolder var3) { - int position = var3.getAdapterPosition(); - Repo repo = mData.get(position); - CoReaderDbHelper.getInstance(getContext()).deleteRepo(Long.parseLong(repo.id)); - if (repo.downloadId > 0) Navigator.startDownloadRepoServiceRemove(getContext(), repo.downloadId); - deleteItem(position); - } - - public void deleteRepo(Repo repo) { - int index = mData.indexOf(repo); - if (index == -1) return; - deleteItem(index); - } - - private void deleteItem(int position) { - mData.remove(position); - notifyItemRemoved(position); - } - - public void clearSubscription() { - mAllSubscription.clear(); - } - - @Override - public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { - LayoutInflater inflater = getLayoutInflater(); - View view; - switch (viewType) { - case R.layout.list_item_main_top_header: - view = inflater.inflate(R.layout.list_item_main_top_header, parent, false); - return new MainHeaderHolder(view); - default: - view = inflater.inflate(R.layout.list_item_repo, parent, false); - return new RepoViewHolder(view); - } - } - - @Override - public int getItemViewType(int position) { - if (position == 0) return R.layout.list_item_main_top_header; - return R.layout.list_item_repo; - } - - public class RepoViewHolder extends RecyclerView.ViewHolder implements Extension { - - @BindView(R.id.img_repo_type) - ImageView mImgRepoType; - @BindView(R.id.text_repo_name) - TextView mTextRepoName; - @BindView(R.id.text_repo_time) - TextView mTextRepoTime; - @BindView(R.id.view_progress_list_repo) - ForegroundProgressRelativeLayout mProgressRelativeLayout; - @BindView(R.id.view_list_repo_action_delete) - View mActionDeleteView; - @BindView(R.id.view_list_repo_action_update) - View mActionSyncView; - @BindView(R.id.view_list_repo_action_container) - View mActionContainer; - @BindView(R.id.img_list_repo_cloud) - View mCloud; - @BindView(R.id.img_list_repo_phone) - View mLocalPhone; - - Subscription mSubscription; - - public RepoViewHolder(View itemView) { - super(itemView); - ButterKnife.bind(this, itemView); - } - - public Subscription bind(Repo repo) { - mImgRepoType.setBackgroundResource(repo.isFolder ? R.drawable.shape_circle_folder : R.drawable.shape_circle_document); - mImgRepoType.setImageResource(repo.isFolder ? R.drawable.ic_repo_white : R.drawable.ic_document_white); - mTextRepoName.setText(repo.name); - mTextRepoTime.setText(DateUtils.getRelativeTimeSpanString(itemView.getContext(), repo.lastModify)); - mActionSyncView.setVisibility(repo.isNetRepo() ? View.VISIBLE : View.GONE); - mCloud.setVisibility(repo.isNetRepo() ? View.VISIBLE : View.GONE); - mLocalPhone.setVisibility(repo.isLocalRepo() ? View.VISIBLE : View.GONE); - resetSubscription(repo); - if (repo.isDownloading()) { - mProgressRelativeLayout.setInitProgress(repo.factor); - } else { - mProgressRelativeLayout.setInitProgress(1f); - } - mProgressRelativeLayout.setUnzip(repo.isUnzip); - return mSubscription; - } - - private void resetSubscription(Repo repo) { - if (mSubscription != null && !mSubscription.isUnsubscribed()) { - mSubscription.unsubscribe(); - } - mSubscription = RxBus.getInstance() - .toObservable() - .filter(o -> o instanceof DownloadProgressEvent) - .map(o -> (DownloadProgressEvent) o) - .filter(o -> (o.downloadId == repo.downloadId) || repo.id.equals(o.repoId)) - .observeOn(AndroidSchedulers.mainThread()) - .doOnNext(o -> { - if (repo.downloadId == 0) repo.downloadId = o.downloadId; - }) - .doOnNext(o -> mProgressRelativeLayout.setProgressCurrent(o.factor)) - .filter(o -> o.factor == 1f) - .doOnNext(o -> repo.isUnzip = o.isUnzip) - .doOnNext(o -> mProgressRelativeLayout.setUnzip(o.isUnzip)) - .filter(o -> o.isUnzip == false) - .doOnNext(o -> repo.downloadId = 0) - .subscribe(); - } - - @Override - public float getActionWidth() { - return mActionContainer.getWidth(); - } - - } - - class MainHeaderHolder extends RecyclerView.ViewHolder { - - @BindView(R.id.grid_main) - GridView mGridView; - private MainHeaderAdapter mMainHeaderAdapter; - - public MainHeaderHolder(View itemView) { - super(itemView); - ButterKnife.bind(this, itemView); - mMainHeaderAdapter = new MainHeaderAdapter(itemView.getContext()); - mGridView.setAdapter(mMainHeaderAdapter); - } - - public void bind() { - List items = new ArrayList<>(); - items.add(new MainHeaderItem(R.drawable.ic_github, R.string.header_item_github_search - , itemView.getContext().getString(R.string.header_item_github_search_link))); - items.add(new MainHeaderItem(R.drawable.ic_trending, R.string.header_item_trending - , itemView.getContext().getString(R.string.header_item_trending_link))); - mMainHeaderAdapter.updateData(items); - } - } -} diff --git a/app/src/main/java/com/loopeer/codereader/ui/adapter/RecyclerViewAdapter.java b/app/src/main/java/com/loopeer/codereader/ui/adapter/RecyclerViewAdapter.java deleted file mode 100644 index d987a67..0000000 --- a/app/src/main/java/com/loopeer/codereader/ui/adapter/RecyclerViewAdapter.java +++ /dev/null @@ -1,56 +0,0 @@ -package com.loopeer.codereader.ui.adapter; - -import android.content.Context; -import android.support.v7.widget.RecyclerView; -import android.view.LayoutInflater; - -import java.util.ArrayList; -import java.util.List; - -public abstract class RecyclerViewAdapter extends RecyclerView.Adapter { - private final Context mContext; - private final LayoutInflater mInflater; - protected List mData; - - public RecyclerViewAdapter(Context context) { - this.mContext = context; - this.mInflater = LayoutInflater.from(context); - this.mData = new ArrayList<>(); - } - - public void updateData(List data) { - this.setData(data); - this.notifyDataSetChanged(); - } - - public void setData(List data) { - this.mData.clear(); - if (data != null) { - this.mData.addAll(data); - } - - } - - public LayoutInflater getLayoutInflater() { - return this.mInflater; - } - - public Context getContext() { - return this.mContext; - } - - public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { - T data = this.getItem(position); - this.bindView(data, position, holder); - } - - public abstract void bindView(T var1, int var2, RecyclerView.ViewHolder var3); - - public T getItem(int position) { - return this.mData.get(position); - } - - public int getItemCount() { - return this.mData == null ? 0 : this.mData.size(); - } -} \ No newline at end of file diff --git a/app/src/main/java/com/loopeer/codereader/ui/adapter/RepositoryAdapter.java b/app/src/main/java/com/loopeer/codereader/ui/adapter/RepositoryAdapter.java deleted file mode 100644 index 35e4e70..0000000 --- a/app/src/main/java/com/loopeer/codereader/ui/adapter/RepositoryAdapter.java +++ /dev/null @@ -1,102 +0,0 @@ -package com.loopeer.codereader.ui.adapter; - -import android.content.Context; -import android.support.v7.widget.RecyclerView; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.ImageView; -import android.widget.TextView; - -import com.bumptech.glide.Glide; -import com.loopeer.codereader.Navigator; -import com.loopeer.codereader.R; -import com.loopeer.codereader.model.Repository; - -import butterknife.BindView; -import butterknife.ButterKnife; - -public class RepositoryAdapter extends RecyclerViewAdapter { - - private boolean mHasMore; - - public RepositoryAdapter(Context context) { - super(context); - } - - public void setHasMore(boolean hasMore) { - mHasMore = hasMore; - } - - @Override - public void bindView(Repository repository, int position, RecyclerView.ViewHolder viewHolder) { - if (viewHolder instanceof RepositoryViewHolder) { - RepositoryViewHolder holder = (RepositoryViewHolder) viewHolder; - holder.bind(repository, position); - } - } - - @Override - public Repository getItem(int position) { - if (isFooterPositon(position)) - return null; - return super.getItem(position); - } - - @Override - public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { - switch (viewType) { - case R.layout.view_footer_loading: { - View view = LayoutInflater.from(getContext()).inflate(R.layout.view_footer_loading, parent, false); - return new RecyclerView.ViewHolder(view) { - }; - } - default: { - View view = LayoutInflater.from(getContext()).inflate(R.layout.list_item_repository, parent, false); - return new RepositoryViewHolder(view); - } - } - } - - private boolean isFooterPositon(int position) { - if (mHasMore && position == getItemCount() - 1) - return true; - return false; - } - - @Override - public int getItemViewType(int position) { - if (isFooterPositon(position)) - return R.layout.view_footer_loading; - return R.layout.list_item_repository; - } - - @Override - public int getItemCount() { - return super.getItemCount() + (mHasMore ? 1 : 0); - } - - class RepositoryViewHolder extends RecyclerView.ViewHolder { - @BindView(R.id.img_avatar) - ImageView mImgAvatar; - @BindView(R.id.txt_full_name) - TextView mTxtFullName; - @BindView(R.id.txt_description) - TextView mTxtDescription; - - RepositoryViewHolder(View view) { - super(view); - ButterKnife.bind(this, view); - } - - public void bind(Repository repository, int position) { - Glide.with(getContext()).load(repository.owner.avatarUrl).into(mImgAvatar); - mTxtFullName.setText(repository.fullName); - mTxtDescription.setText(repository.description); - - itemView.setOnClickListener(view -> { - Navigator.startWebActivity(getContext(), repository.htmlUrl); - }); - } - } -} diff --git a/app/src/main/java/com/loopeer/codereader/ui/decoration/AppbarBehavior.java b/app/src/main/java/com/loopeer/codereader/ui/decoration/AppbarBehavior.java deleted file mode 100644 index fae6d2a..0000000 --- a/app/src/main/java/com/loopeer/codereader/ui/decoration/AppbarBehavior.java +++ /dev/null @@ -1,25 +0,0 @@ -package com.loopeer.codereader.ui.decoration; - -import android.content.Context; -import android.support.design.widget.AppBarLayout; -import android.support.design.widget.CoordinatorLayout; -import android.util.AttributeSet; -import android.view.View; - -public class AppbarBehavior extends AppBarLayout.Behavior { - - public AppbarBehavior() { - } - - public AppbarBehavior(Context context, AttributeSet attrs) { - super(context, attrs); - } - - @Override - public boolean onStartNestedScroll(CoordinatorLayout parent, AppBarLayout child, View directTargetChild, View target, int nestedScrollAxes) { - boolean result = super.onStartNestedScroll(parent, child, directTargetChild, target, nestedScrollAxes); - if (result && !target.canScrollVertically(1) && child.getY() >= 0) - result = false; - return result; - } -} diff --git a/app/src/main/java/com/loopeer/codereader/ui/decoration/DividerItemDecoration.java b/app/src/main/java/com/loopeer/codereader/ui/decoration/DividerItemDecoration.java deleted file mode 100644 index ea2e23f..0000000 --- a/app/src/main/java/com/loopeer/codereader/ui/decoration/DividerItemDecoration.java +++ /dev/null @@ -1,164 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.loopeer.codereader.ui.decoration; - -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.Rect; -import android.support.v4.content.ContextCompat; -import android.support.v4.view.ViewCompat; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; -import android.view.View; - -import com.loopeer.codereader.R; - -public class DividerItemDecoration extends RecyclerView.ItemDecoration { - private static final int DEFAULT_DIVIDER_HEIGHT = 1; - - public static final int HORIZONTAL_LIST = LinearLayoutManager.HORIZONTAL; - - public static final int VERTICAL_LIST = LinearLayoutManager.VERTICAL; - - protected int mOrientation; - protected int padding; - protected int startpadding; - protected int endpadding; - protected int dividerHeight; - protected Context mContext; - protected Paint mPaddingPaint; - protected Paint mDividerPaint; - - public DividerItemDecoration(Context context) { - this(context, VERTICAL_LIST, -1, -1); - } - - public DividerItemDecoration(Context context, int orientation) { - this(context, orientation, -1, -1); - } - - public DividerItemDecoration(Context context, int orientation, int padding, int dividerHeight) { - setOrientation(orientation); - mContext = context; - - init(); - if (padding != -1) this.padding = padding; - updatePaddint(); - if (dividerHeight != -1) this.dividerHeight = dividerHeight; - } - - public DividerItemDecoration(Context context, int orientation, int startpadding, int endpadding, int dividerHeight) { - setOrientation(orientation); - mContext = context; - - init(); - if (startpadding != -1) this.startpadding = startpadding; - if (endpadding != -1) this.endpadding = endpadding; - if (dividerHeight != -1) this.dividerHeight = dividerHeight; - } - - private void updatePaddint() { - startpadding = padding; - endpadding = padding; - } - - private void init() { - padding = mContext.getResources().getDimensionPixelSize(R.dimen.medium_padding); - updatePaddint(); - dividerHeight = DEFAULT_DIVIDER_HEIGHT; - - mPaddingPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - mPaddingPaint.setColor(ContextCompat.getColor(mContext, R.color.item_background)); - mPaddingPaint.setStyle(Paint.Style.FILL); - - mDividerPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - mDividerPaint.setColor(ContextCompat.getColor(mContext, R.color.color_divider)); - mDividerPaint.setStyle(Paint.Style.FILL); - } - - public void setOrientation(int orientation) { - if (orientation != HORIZONTAL_LIST && orientation != VERTICAL_LIST) { - throw new IllegalArgumentException("invalid orientation"); - } - mOrientation = orientation; - } - - @Override - public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) { - super.onDraw(c, parent, state); - if (mOrientation == VERTICAL_LIST) { - drawVertical(c, parent); - } else { - drawHorizontal(c, parent); - } - } - - public void drawVertical(Canvas c, RecyclerView parent) { - final int left = parent.getPaddingLeft(); - final int right = parent.getWidth() - parent.getPaddingRight(); - - final int childCount = parent.getChildCount(); - for (int i = 0; i < childCount - 1; i++) { - final View child = parent.getChildAt(i); - final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams(); - final int top = child.getBottom() + params.bottomMargin + - Math.round(ViewCompat.getTranslationY(child)); - final int bottom = top + dividerHeight; - - c.drawRect(left, top, left + startpadding, bottom, mPaddingPaint); - c.drawRect(right - endpadding, top, right, bottom, mPaddingPaint); - c.drawRect(left + startpadding, top, right - endpadding, bottom, mDividerPaint); - } - } - - public void drawHorizontal(Canvas c, RecyclerView parent) { - final int top = parent.getPaddingTop(); - final int bottom = parent.getHeight() - parent.getPaddingBottom(); - - final int childCount = parent.getChildCount(); - for (int i = 0; i < childCount - 1; i++) { - final View child = parent.getChildAt(i); - final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams(); - final int left = child.getRight() + params.rightMargin + - Math.round(ViewCompat.getTranslationX(child)); - final int right = left + dividerHeight; - c.drawRect(left, top, right, top + startpadding, mPaddingPaint); - c.drawRect(left, bottom - endpadding, right, bottom, mPaddingPaint); - c.drawRect(left, top + startpadding, right, bottom - endpadding, mDividerPaint); - } - } - - @Override - public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { - super.getItemOffsets(outRect, view, parent, state); - if (mOrientation == VERTICAL_LIST) { - if (parent.getChildAdapterPosition(view) != parent.getAdapter().getItemCount() - 1) { - outRect.set(0, 0, 0, dividerHeight); - } else { - outRect.set(0, 0, 0, 0); - } - } else { - if (parent.getChildAdapterPosition(view) != parent.getAdapter().getItemCount() - 1) { - outRect.set(0, 0, dividerHeight, 0); - } else { - outRect.set(0, 0, 0, 0); - } - } - - } -} diff --git a/app/src/main/java/com/loopeer/codereader/ui/decoration/DividerItemDecorationMainList.java b/app/src/main/java/com/loopeer/codereader/ui/decoration/DividerItemDecorationMainList.java deleted file mode 100644 index 39a4770..0000000 --- a/app/src/main/java/com/loopeer/codereader/ui/decoration/DividerItemDecorationMainList.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright (C) 2014 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.loopeer.codereader.ui.decoration; - -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Rect; -import android.support.v4.view.ViewCompat; -import android.support.v7.widget.RecyclerView; -import android.view.View; - -import com.loopeer.codereader.R; - -public class DividerItemDecorationMainList extends DividerItemDecoration { - public DividerItemDecorationMainList(Context context) { - super(context); - } - - public DividerItemDecorationMainList(Context context, int orientation) { - super(context, orientation); - } - - public DividerItemDecorationMainList(Context context, int orientation, int padding, int dividerHeight) { - super(context, orientation, padding, dividerHeight); - } - - public DividerItemDecorationMainList(Context context, int orientation, int startpadding, int endpadding, int dividerHeight) { - super(context, orientation, startpadding, endpadding, dividerHeight); - } - - public void drawVertical(Canvas c, RecyclerView parent) { - final int left = parent.getPaddingLeft(); - final int right = parent.getWidth() - parent.getPaddingRight(); - - final int childCount = parent.getChildCount(); - - for (int i = 0; i < childCount - 1; i++) { - if (i == 0) { - continue; - } - final View child = parent.getChildAt(i); - final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams) child.getLayoutParams(); - final int top = child.getBottom() + params.bottomMargin + - Math.round(ViewCompat.getTranslationY(child)); - final int bottom = top + dividerHeight; - - c.drawRect(left, top, left + startpadding, bottom, mPaddingPaint); - c.drawRect(right - endpadding, top, right, bottom, mPaddingPaint); - c.drawRect(left + startpadding, top, right - endpadding, bottom, mDividerPaint); - } - } - - @Override - public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { - super.getItemOffsets(outRect, view, parent, state); - if (mOrientation == VERTICAL_LIST) { - if (parent.getChildAdapterPosition(view) == 0) { - outRect.set(0, 0, 0, mContext.getResources().getDimensionPixelSize(R.dimen.medium_padding)); - } else if (parent.getChildAdapterPosition(view) != parent.getAdapter().getItemCount() - 1) { - outRect.set(0, 0, 0, dividerHeight); - } else { - outRect.set(0, 0, 0, 0); - } - } else { - if (parent.getChildAdapterPosition(view) != parent.getAdapter().getItemCount() - 1) { - outRect.set(0, 0, dividerHeight, 0); - } else { - outRect.set(0, 0, 0, 0); - } - } - - } -} diff --git a/app/src/main/java/com/loopeer/codereader/ui/fragment/BaseFragment.java b/app/src/main/java/com/loopeer/codereader/ui/fragment/BaseFragment.java deleted file mode 100644 index d1887b7..0000000 --- a/app/src/main/java/com/loopeer/codereader/ui/fragment/BaseFragment.java +++ /dev/null @@ -1,112 +0,0 @@ -package com.loopeer.codereader.ui.fragment; - -import android.os.Bundle; -import android.support.annotation.Nullable; -import android.support.v4.app.Fragment; -import android.text.TextUtils; -import android.view.View; - -import com.loopeer.codereader.R; -import com.loopeer.codereader.ui.view.ProgressLoading; - -import butterknife.ButterKnife; -import rx.Subscription; -import rx.subscriptions.CompositeSubscription; - -public class BaseFragment extends Fragment { - private final CompositeSubscription mAllSubscription = new CompositeSubscription(); - - @Override - public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - ButterKnife.bind(this, view); - } - - - protected void registerSubscription(Subscription subscription) { - mAllSubscription.add(subscription); - } - - protected void unregisterSubscription(Subscription subscription) { - mAllSubscription.remove(subscription); - } - - protected void clearSubscription() { - mAllSubscription.clear(); - } - - @Override - public void onDestroyView() { - super.onDestroyView(); - clearSubscription(); - if (isProgressShow() && mProgressLoading != null) { - dismissProgressLoading(); - mProgressLoading = null; - } - } - - private ProgressLoading mProgressLoading; - private ProgressLoading mUnBackProgressLoading; - private boolean progressShow; - - public ProgressLoading getProgressLoading() { - return mProgressLoading; - } - - public void showProgressLoading(int resId) { - showProgressLoading(getString(resId)); - } - - public void showProgressLoading(String message) { - if (mProgressLoading == null) { - mProgressLoading = new ProgressLoading(getActivity(), R.style.ProgressLoadingTheme); - mProgressLoading.setCanceledOnTouchOutside(true); - mProgressLoading.setOnCancelListener(dialog -> progressShow = false); - } - if (!TextUtils.isEmpty(message)) { - mProgressLoading.setMessage(message); - } else { - mProgressLoading.setMessage(null); - } - progressShow = true; - mProgressLoading.show(); - } - - public boolean isProgressShow() { - return progressShow; - } - - public void dismissProgressLoading() { - if (mProgressLoading != null && isVisible()) { - progressShow = false; - mProgressLoading.dismiss(); - } - } - - public void showUnBackProgressLoading(int resId) { - showUnBackProgressLoading(getString(resId)); - } - - public void showUnBackProgressLoading(String message) { - if (mUnBackProgressLoading == null) { - mUnBackProgressLoading = new ProgressLoading(getActivity(), R.style.ProgressLoadingTheme) { - @Override - public void onBackPressed() { - } - }; - } - if (!TextUtils.isEmpty(message)) { - mUnBackProgressLoading.setMessage(message); - } else { - mUnBackProgressLoading.setMessage(null); - } - mUnBackProgressLoading.show(); - } - - public void dismissUnBackProgressLoading() { - if (mUnBackProgressLoading != null && isVisible()) { - mUnBackProgressLoading.dismiss(); - } - } - -} diff --git a/app/src/main/java/com/loopeer/codereader/ui/fragment/BaseFullscreenFragment.java b/app/src/main/java/com/loopeer/codereader/ui/fragment/BaseFullscreenFragment.java deleted file mode 100644 index 356c16e..0000000 --- a/app/src/main/java/com/loopeer/codereader/ui/fragment/BaseFullscreenFragment.java +++ /dev/null @@ -1,73 +0,0 @@ -package com.loopeer.codereader.ui.fragment; - -import android.os.Bundle; -import android.os.Handler; -import android.support.annotation.Nullable; -import android.view.View; - -public class BaseFullscreenFragment extends BaseFragment { - - private static final boolean AUTO_HIDE = true; - - private static final int AUTO_HIDE_DELAY_MILLIS = 3000; - private static final int UI_ANIMATION_DELAY = 300; - - private View mDecorView; - - private final Handler mHideHandler = new Handler(); - - private final Runnable mHideRunnable = new Runnable() { - @Override - public void run() { - mDecorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LOW_PROFILE - | View.SYSTEM_UI_FLAG_FULLSCREEN - | View.SYSTEM_UI_FLAG_LAYOUT_STABLE - | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY - | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION - | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION); - } - }; - - private boolean mVisible; - - protected final View.OnTouchListener mDelayHideTouchListener = (view, motionEvent) -> { - if (AUTO_HIDE) { - delayedHide(AUTO_HIDE_DELAY_MILLIS); - } - return false; - }; - - @Override - public void onActivityCreated(@Nullable Bundle savedInstanceState) { - super.onActivityCreated(savedInstanceState); - mDecorView = getActivity().getWindow().getDecorView(); - mVisible = true; - } - - private void toggle() { - if (mVisible) { - hide(); - } else { - show(); - } - } - - protected void hide() { - mVisible = false; - mHideHandler.postDelayed(mHideRunnable, UI_ANIMATION_DELAY); - } - - protected void show() { - mDecorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN - | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION); - mVisible = true; - - mHideHandler.removeCallbacks(mHideRunnable); - } - - protected void delayedHide(int delayMillis) { - mHideHandler.removeCallbacks(mHideRunnable); - mHideHandler.postDelayed(mHideRunnable, delayMillis); - } - -} diff --git a/app/src/main/java/com/loopeer/codereader/ui/fragment/CodeReadFragment.java b/app/src/main/java/com/loopeer/codereader/ui/fragment/CodeReadFragment.java deleted file mode 100644 index 270e588..0000000 --- a/app/src/main/java/com/loopeer/codereader/ui/fragment/CodeReadFragment.java +++ /dev/null @@ -1,365 +0,0 @@ -package com.loopeer.codereader.ui.fragment; - -import android.annotation.TargetApi; -import android.content.res.Configuration; -import android.graphics.Bitmap; -import android.os.Build; -import android.os.Bundle; -import android.support.annotation.Nullable; -import android.support.design.widget.AppBarLayout; -import android.support.v4.content.ContextCompat; -import android.support.v7.app.AppCompatActivity; -import android.support.v7.widget.Toolbar; -import android.text.TextUtils; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.webkit.WebChromeClient; -import android.webkit.WebResourceRequest; -import android.webkit.WebView; -import android.webkit.WebViewClient; - -import com.loopeer.codereader.Navigator; -import com.loopeer.codereader.R; -import com.loopeer.codereader.model.DirectoryNode; -import com.loopeer.codereader.ui.loader.CodeFragmentContentLoader; -import com.loopeer.codereader.ui.loader.ILoadHelper; -import com.loopeer.codereader.ui.view.NestedScrollWebView; -import com.loopeer.codereader.utils.BrushMap; -import com.loopeer.codereader.utils.ColorUtils; -import com.loopeer.codereader.utils.DeviceUtils; -import com.loopeer.codereader.utils.FileTypeUtils; -import com.loopeer.codereader.utils.HtmlParser; -import com.todou.markdownj.MarkdownProcessor; - -import java.io.BufferedReader; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.util.concurrent.TimeUnit; - -import butterknife.BindView; -import rx.Observable; -import rx.Subscriber; -import rx.Subscription; -import rx.android.schedulers.AndroidSchedulers; -import rx.schedulers.Schedulers; - -public class CodeReadFragment extends BaseFullscreenFragment implements NestedScrollWebView.ScrollChangeListener { - private static final String TAG = "CodeReadFragment"; - - @BindView(R.id.web_code_read) - NestedScrollWebView mWebCodeRead; - @BindView(R.id.toolbar) - Toolbar mToolbar; - - private DirectoryNode mNode; - private DirectoryNode mRootNode; - private Subscription scrollFinishDelaySubscription; - private boolean mScrollDown = false; - private boolean mOpenFileAfterLoadFinish = false; - private ILoadHelper mCodeContentLoader; - - private boolean mOrientationChange; - - public static CodeReadFragment newInstance(DirectoryNode node, DirectoryNode root) { - CodeReadFragment codeReadFragment = new CodeReadFragment(); - codeReadFragment.mNode = node; - codeReadFragment.mRootNode = root; - return codeReadFragment; - } - - @Nullable - @Override - public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container - , @Nullable Bundle savedInstanceState) { - return inflater.inflate(R.layout.fragment_code_read, container, false); - } - - @Override - public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - mCodeContentLoader = new CodeFragmentContentLoader(view); - - setupToolbar(); - - AppCompatActivity activity = (AppCompatActivity) getActivity(); - activity.setSupportActionBar(mToolbar); - activity.getSupportActionBar().setDisplayHomeAsUpEnabled(true); - activity.getSupportActionBar().setHomeAsUpIndicator(ContextCompat.getDrawable(getContext() - , R.drawable.ic_view_list_white)); - mWebCodeRead.setScrollChangeListener(this); - mWebCodeRead.getSettings().setJavaScriptEnabled(true); - mWebCodeRead.getSettings().setSupportZoom(true); - mWebCodeRead.getSettings().setBuiltInZoomControls(true); - mWebCodeRead.setWebViewClient(new WebViewClient() { - @Override - public void onPageStarted(WebView view, String url, Bitmap favicon) { - super.onPageStarted(view, url, favicon); - mCodeContentLoader.showContent(); - } - - @Override - public boolean shouldOverrideUrlLoading(WebView view, String url) { - Navigator.startWebActivity(getContext(), url); - return true; - } - - @TargetApi(Build.VERSION_CODES.LOLLIPOP) - @Override - public boolean shouldOverrideUrlLoading(WebView view, WebResourceRequest request) { - Navigator.startWebActivity(getContext(), String.valueOf(request.getUrl())); - return true; - } - }); - - mWebCodeRead.setWebChromeClient(new WebChromeClient() { - - }); - if (Build.VERSION.SDK_INT >= 11) { - ((Runnable) () -> mWebCodeRead.getSettings().setDisplayZoomControls(false)).run(); - } - openFile(); - } - - private void setupToolbar() { - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) - return; - - AppBarLayout.LayoutParams params = (AppBarLayout.LayoutParams) mToolbar.getLayoutParams(); - params.height = (int) (DeviceUtils.dpToPx(getActivity(), 56) - + DeviceUtils.getStatusBarHeight()); - mToolbar.setLayoutParams(params); - mToolbar.setPadding(0, DeviceUtils.getStatusBarHeight(), 0, 0); - } - - private void openFile() { - mCodeContentLoader.showProgress(); - if (mWebCodeRead == null) { - return; - } - mWebCodeRead.clearHistory(); - if (mNode == null) { - if (mOpenFileAfterLoadFinish) - mCodeContentLoader.showEmpty(getString(R.string.code_read_no_file_open)); - } else if (FileTypeUtils.isImageFileType(mNode.absolutePath)) { - openImageFile(); - } else if (FileTypeUtils.isMdFileType(mNode.absolutePath)) { - openMdShowFile(); - } else { - openCodeFile(); - } - } - - private void openImageFile() { - String string = "" + - "" - + "" - + ""; - mWebCodeRead.loadDataWithBaseURL(null, string - , "text/html" - , "utf-8" - , null); - } - - public void openFile(DirectoryNode node) { - mOpenFileAfterLoadFinish = true; - mNode = node; - if (!isVisible()) return; - openFile(); - } - - protected void openCodeFile() { - Observable.create(new Observable.OnSubscribe() { - @Override - public void call(Subscriber subscriber) { - InputStream stream = null; - try { - stream = new FileInputStream(mNode.absolutePath); - } catch (FileNotFoundException e) { - e.printStackTrace(); - } - if (stream == null) { - subscriber.onCompleted(); - return; - } - final InputStream finalStream = stream; - String[] names = mNode.name.split("\\."); - String fileTypeName = names[names.length - 1]; - if (BrushMap.isBlackFile(fileTypeName)) { - subscriber.onError(new Throwable("Can not open this file!")); - subscriber.onCompleted(); - return; - } - String jsFile = BrushMap.getJsFileForExtension(fileTypeName); - if (jsFile == null) { - jsFile = "Txt"; - } - StringBuilder sb = new StringBuilder(); - StringBuilder localStringBuilder = new StringBuilder(); - try { - BufferedReader localBufferedReader = new BufferedReader( - new InputStreamReader(finalStream, "UTF-8")); - for (; ; ) { - String str = localBufferedReader.readLine(); - if (str == null) { - break; - } - localStringBuilder.append(str); - localStringBuilder.append("\n"); - } - - localBufferedReader.close(); - sb.append("
");
-                    sb.append(TextUtils.htmlEncode(localStringBuilder.toString()));
-                    sb.append("
"); - subscriber.onNext(HtmlParser.buildHtmlContent(getActivity(), sb.toString() - , jsFile, mNode.name)); - } catch (OutOfMemoryError e) { - subscriber.onError(e); - } catch (FileNotFoundException e) { - subscriber.onError(e); - } catch (IOException e) { - subscriber.onError(e); - } - subscriber.onCompleted(); - } - }) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .doOnNext(o -> mWebCodeRead.loadDataWithBaseURL("file:///android_asset/" - , o, "text/html", "UTF-8", "")) - .doOnError(e -> mCodeContentLoader.showEmpty(e.getMessage())) - .onErrorResumeNext(Observable.empty()) - .subscribe(); - } - - protected void openMdShowFile() { - registerSubscription( - Observable.create(new Observable.OnSubscribe() { - @Override - public void call(Subscriber subscriber) { - InputStream stream = null; - try { - stream = new FileInputStream(mNode.absolutePath); - } catch (FileNotFoundException e) { - subscriber.onError(e); - } - if (stream == null) - return; - final InputStream finalStream = stream; - StringBuilder localStringBuilder = new StringBuilder(); - try { - BufferedReader localBufferedReader = new BufferedReader( - new InputStreamReader(finalStream, "UTF-8")); - for (; ; ) { - String str = localBufferedReader.readLine(); - if (str == null) { - break; - } - localStringBuilder.append(str); - localStringBuilder.append("\n"); - } - String textString = localStringBuilder.toString(); - - if (textString != null) { - MarkdownProcessor m = new MarkdownProcessor(mRootNode.absolutePath); - m.setTextColorString(ColorUtils.getColorString(getContext() - , R.color.text_color_primary)); - m.setBackgroundColorString(ColorUtils.getColorString(getContext() - , R.color.code_read_background_color)); - m.setCodeBlockColor(ColorUtils.getColorString(getContext() - , R.color.code_block_color)); - m.setTableBorderColor(ColorUtils.getColorString(getContext() - , R.color.table_block_border_color)); - String html = m.markdown(textString); - subscriber.onNext(html); - } - subscriber.onCompleted(); - } catch (OutOfMemoryError e) { - subscriber.onError(e); - } catch (FileNotFoundException e) { - subscriber.onError(e); - } catch (IOException e) { - subscriber.onError(e); - } - } - }) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .doOnNext(s -> mWebCodeRead.loadDataWithBaseURL("fake://", s, "text/html" - , "UTF-8", "")) - .doOnError(e -> mCodeContentLoader.showEmpty(e.getMessage())) - .onErrorResumeNext(Observable.empty()) - .subscribe() - ); - } - - @Override - public void onDestroyView() { - super.onDestroyView(); - - mWebCodeRead.destroy(); - } - - @Override - public void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); - mOrientationChange = true; - } - - @Override - public void onScrollChanged(int l, int t, int oldl, int oldt) { - if (mOrientationChange) { - mOrientationChange = false; - return; - } - - if (scrollFinishDelaySubscription != null && !scrollFinishDelaySubscription.isUnsubscribed()) { - scrollFinishDelaySubscription.unsubscribe(); - } - if (t - oldt > 70) { - if (mScrollDown) - return; - - mScrollDown = true; - } else if (t - oldt < 0) { - if (!mScrollDown) - return; - - mScrollDown = false; - closeFullScreen(); - } - if (mScrollDown) { - scrollFinishDelaySubscription = Observable - .timer(500, TimeUnit.MILLISECONDS) - .observeOn(AndroidSchedulers.mainThread()) - .doOnNext(lo -> openFullScreen()) - .subscribe(); - registerSubscription(scrollFinishDelaySubscription); - } - } - - public void updateRootNode(DirectoryNode directoryNode) { - mRootNode = directoryNode; - } - - public ILoadHelper getCodeContentLoader() { - return mCodeContentLoader; - } - - private void openFullScreen() { - hide(); - } - - private void closeFullScreen() { - show(); - } - -} diff --git a/app/src/main/java/com/loopeer/codereader/ui/loader/CodeFragmentContentLoader.java b/app/src/main/java/com/loopeer/codereader/ui/loader/CodeFragmentContentLoader.java deleted file mode 100644 index 76e9fc5..0000000 --- a/app/src/main/java/com/loopeer/codereader/ui/loader/CodeFragmentContentLoader.java +++ /dev/null @@ -1,44 +0,0 @@ -package com.loopeer.codereader.ui.loader; - -import android.view.View; -import android.widget.TextView; -import android.widget.ViewAnimator; - -import com.loopeer.codereader.R; -import com.loopeer.codereader.ui.view.ProgressIndicatorView; - -import butterknife.BindView; -import butterknife.ButterKnife; - -public class CodeFragmentContentLoader implements ILoadHelper { - - @BindView(R.id.progress_code_fragment) - ProgressIndicatorView mProgressIndicatorView; - @BindView(R.id.content_animator) - ViewAnimator mContentAnimator; - @BindView(android.R.id.empty) - TextView mTextEmpty; - - public CodeFragmentContentLoader(View contentView) { - ButterKnife.bind(this, contentView); - } - - @Override - public void showProgress() { - mContentAnimator.setDisplayedChild(1); - mProgressIndicatorView.setAnimationStatus(ProgressIndicatorView.AnimStatus.START); - } - - @Override - public void showContent() { - mContentAnimator.setDisplayedChild(0); - mProgressIndicatorView.setAnimationStatus(ProgressIndicatorView.AnimStatus.CANCEL); - } - - @Override - public void showEmpty(String message) { - mContentAnimator.setDisplayedChild(2); - mTextEmpty.setText(message); - mProgressIndicatorView.setAnimationStatus(ProgressIndicatorView.AnimStatus.CANCEL); - } -} diff --git a/app/src/main/java/com/loopeer/codereader/ui/loader/ILoadHelper.java b/app/src/main/java/com/loopeer/codereader/ui/loader/ILoadHelper.java deleted file mode 100644 index 424f086..0000000 --- a/app/src/main/java/com/loopeer/codereader/ui/loader/ILoadHelper.java +++ /dev/null @@ -1,11 +0,0 @@ -package com.loopeer.codereader.ui.loader; - -public interface ILoadHelper { - - void showProgress(); - - void showContent(); - - void showEmpty(String message); - -} \ No newline at end of file diff --git a/app/src/main/java/com/loopeer/codereader/ui/loader/RecyclerLoader.java b/app/src/main/java/com/loopeer/codereader/ui/loader/RecyclerLoader.java deleted file mode 100644 index d46a6e1..0000000 --- a/app/src/main/java/com/loopeer/codereader/ui/loader/RecyclerLoader.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.loopeer.codereader.ui.loader; - -import android.widget.ViewAnimator; - -public class RecyclerLoader implements ILoadHelper { - - ViewAnimator mViewAnimator; - - public RecyclerLoader(ViewAnimator continer) { - mViewAnimator = continer; - } - - @Override - public void showProgress() { - mViewAnimator.setDisplayedChild(2); - } - - @Override - public void showContent() { - mViewAnimator.setDisplayedChild(0); - } - - @Override - public void showEmpty(String message) { - mViewAnimator.setDisplayedChild(1); - } -} diff --git a/app/src/main/java/com/loopeer/codereader/ui/package.info b/app/src/main/java/com/loopeer/codereader/ui/package.info deleted file mode 100644 index e69de29..0000000 diff --git a/app/src/main/java/com/loopeer/codereader/ui/view/AddRepoChecker.java b/app/src/main/java/com/loopeer/codereader/ui/view/AddRepoChecker.java deleted file mode 100644 index ff5342d..0000000 --- a/app/src/main/java/com/loopeer/codereader/ui/view/AddRepoChecker.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.loopeer.codereader.ui.view; - -import android.text.TextUtils; - -public class AddRepoChecker extends Checker { - public String repoName; - public String repoDownloadUrl; - - public AddRepoChecker(CheckObserver checkObserver) { - super(checkObserver); - } - - public void setRepoName(String repoName) { - this.repoName = repoName; - mCheckObserver.check(isEnable()); - } - - public void setRepoDownloadUrl(String repoDownloadUrl) { - this.repoDownloadUrl = repoDownloadUrl; - mCheckObserver.check(isEnable()); - } - - @Override - public boolean isEnable() { - return !TextUtils.isEmpty(repoName) && !TextUtils.isEmpty(repoDownloadUrl); - } -} diff --git a/app/src/main/java/com/loopeer/codereader/ui/view/Checker.java b/app/src/main/java/com/loopeer/codereader/ui/view/Checker.java deleted file mode 100644 index 4e40eca..0000000 --- a/app/src/main/java/com/loopeer/codereader/ui/view/Checker.java +++ /dev/null @@ -1,15 +0,0 @@ -package com.loopeer.codereader.ui.view; - -public abstract class Checker { - public interface CheckObserver{ - void check(boolean b); - } - - CheckObserver mCheckObserver; - - public Checker(CheckObserver checkObserver) { - mCheckObserver = checkObserver; - } - - abstract boolean isEnable(); -} diff --git a/app/src/main/java/com/loopeer/codereader/ui/view/DirectoryNavDelegate.java b/app/src/main/java/com/loopeer/codereader/ui/view/DirectoryNavDelegate.java deleted file mode 100644 index e3842dd..0000000 --- a/app/src/main/java/com/loopeer/codereader/ui/view/DirectoryNavDelegate.java +++ /dev/null @@ -1,109 +0,0 @@ -package com.loopeer.codereader.ui.view; - -import android.content.Context; -import android.support.v7.widget.LinearLayoutManager; -import android.support.v7.widget.RecyclerView; -import android.widget.Toast; - -import com.loopeer.codereader.model.DirectoryNode; -import com.loopeer.codereader.ui.adapter.DirectoryAdapter; -import com.loopeer.codereader.utils.FileCache; -import com.loopeer.codereader.utils.FileTypeUtils; - -import java.io.File; - -import rx.Observable; -import rx.android.schedulers.AndroidSchedulers; -import rx.schedulers.Schedulers; -import rx.subscriptions.CompositeSubscription; - -public class DirectoryNavDelegate { - private static final String TAG = "DirectoryNavDelegate"; - - public interface FileClickListener { - void doOpenFile(DirectoryNode node); - } - - public interface LoadFileCallback{ - void onFileOpenStart(); - void onFileOpenEnd(); - } - - private RecyclerView mRecyclerView; - private DirectoryAdapter mDirectoryAdapter; - private Context mContext; - private FileClickListener mFileClickListener; - private LoadFileCallback mLoadFileCallback; - private final CompositeSubscription mAllSubscription = new CompositeSubscription(); - - public DirectoryNavDelegate(RecyclerView recyclerView, FileClickListener listener) { - mRecyclerView = recyclerView; - mContext = recyclerView.getContext(); - mFileClickListener = listener; - mDirectoryAdapter = new DirectoryAdapter(recyclerView.getContext(), listener); - setUpRecyclerView(); - } - - public void setLoadFileCallback(LoadFileCallback loadFileCallback) { - mLoadFileCallback = loadFileCallback; - } - - public void clearSubscription() { - mAllSubscription.clear(); - } - - public void resumeDirectoryState(DirectoryNode node) { - mDirectoryAdapter.setNodeRoot(node); - } - - public DirectoryNode getDirectoryNodeInstance() { - return mDirectoryAdapter.getNodeRoot(); - } - - private void setUpRecyclerView() { - mRecyclerView.setLayoutManager(new LinearLayoutManager(mContext)); - mRecyclerView.setAdapter(mDirectoryAdapter); - } - - public void updateData(DirectoryNode directoryNode) { - mLoadFileCallback.onFileOpenStart(); - mAllSubscription.add( - Observable.fromCallable(() -> { - DirectoryNode node; - if (directoryNode.isDirectory) { - node = FileCache.getFileDirectory(new File(directoryNode.absolutePath)); - } else { - node = directoryNode; - } - return node; - }) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .doOnNext(mDirectoryAdapter::setNodeRoot) - .doOnNext(this::checkOpenFirstFile) - .doOnError(e -> Toast.makeText(mContext, e.getMessage(), Toast.LENGTH_SHORT).show()) - .onErrorResumeNext(Observable.empty()) - .doOnCompleted(() -> mLoadFileCallback.onFileOpenEnd()) - .subscribe()); - } - - private void checkOpenFirstFile(DirectoryNode node) { - if (node.isDirectory && node.pathNodes != null) { - boolean haveOpen = false; - for (DirectoryNode n : node.pathNodes) { - if (FileTypeUtils.isMdFileType(n.name) && n.name.equalsIgnoreCase("readme.md")) { - mFileClickListener.doOpenFile(n); - haveOpen = true; - } - } - if (!haveOpen) { - mFileClickListener.doOpenFile(null); - } - } else if (!node.isDirectory) { - mFileClickListener.doOpenFile(node); - } else { - mFileClickListener.doOpenFile(null); - } - } - -} diff --git a/app/src/main/java/com/loopeer/codereader/ui/view/DrawerLayout.java b/app/src/main/java/com/loopeer/codereader/ui/view/DrawerLayout.java deleted file mode 100644 index 108987a..0000000 --- a/app/src/main/java/com/loopeer/codereader/ui/view/DrawerLayout.java +++ /dev/null @@ -1,2339 +0,0 @@ -package com.loopeer.codereader.ui.view; - -import android.content.Context; -import android.content.res.TypedArray; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.PixelFormat; -import android.graphics.Rect; -import android.graphics.drawable.ColorDrawable; -import android.graphics.drawable.Drawable; -import android.os.Build; -import android.os.Parcel; -import android.os.Parcelable; -import android.os.SystemClock; -import android.support.annotation.ColorInt; -import android.support.annotation.DrawableRes; -import android.support.annotation.IntDef; -import android.support.annotation.NonNull; -import android.support.annotation.Nullable; -import android.support.v4.content.ContextCompat; -import android.support.v4.graphics.drawable.DrawableCompat; -import android.support.v4.os.ParcelableCompat; -import android.support.v4.os.ParcelableCompatCreatorCallbacks; -import android.support.v4.view.AbsSavedState; -import android.support.v4.view.AccessibilityDelegateCompat; -import android.support.v4.view.GravityCompat; -import android.support.v4.view.KeyEventCompat; -import android.support.v4.view.MotionEventCompat; -import android.support.v4.view.ViewCompat; -import android.support.v4.view.ViewGroupCompat; -import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat; -import android.util.AttributeSet; -import android.view.Gravity; -import android.view.KeyEvent; -import android.view.MotionEvent; -import android.view.View; -import android.view.ViewGroup; -import android.view.ViewParent; -import android.view.accessibility.AccessibilityEvent; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.util.ArrayList; -import java.util.List; - -public class DrawerLayout extends ViewGroup implements DrawerLayoutImpl { - private static final String TAG = "DrawerLayout"; - - @IntDef({STATE_IDLE, STATE_DRAGGING, STATE_SETTLING}) - @Retention(RetentionPolicy.SOURCE) - private @interface State {} - - /** - * Indicates that any drawers are in an idle, settled state. No animation is in progress. - */ - public static final int STATE_IDLE = ViewDragHelper.STATE_IDLE; - - /** - * Indicates that a drawer is currently being dragged by the user. - */ - public static final int STATE_DRAGGING = ViewDragHelper.STATE_DRAGGING; - - /** - * Indicates that a drawer is in the process of settling to a final position. - */ - public static final int STATE_SETTLING = ViewDragHelper.STATE_SETTLING; - - /** @hide */ - @IntDef({LOCK_MODE_UNLOCKED, LOCK_MODE_LOCKED_CLOSED, LOCK_MODE_LOCKED_OPEN, - LOCK_MODE_UNDEFINED}) - @Retention(RetentionPolicy.SOURCE) - private @interface LockMode {} - - /** - * The drawer is unlocked. - */ - public static final int LOCK_MODE_UNLOCKED = 0; - - /** - * The drawer is locked closed. The user may not open it, though - * the app may open it programmatically. - */ - public static final int LOCK_MODE_LOCKED_CLOSED = 1; - - /** - * The drawer is locked open. The user may not close it, though the app - * may close it programmatically. - */ - public static final int LOCK_MODE_LOCKED_OPEN = 2; - - /** - * The drawer's lock state is reset to default. - */ - public static final int LOCK_MODE_UNDEFINED = 3; - - /** @hide */ - @IntDef({Gravity.LEFT, Gravity.RIGHT, GravityCompat.START, GravityCompat.END}) - @Retention(RetentionPolicy.SOURCE) - private @interface EdgeGravity {} - - - private static final int MIN_DRAWER_MARGIN = 64; // dp - private static final int DRAWER_ELEVATION = 10; //dp - - private static final int DEFAULT_SCRIM_COLOR = 0x99000000; - - /** - * Length of time to delay before peeking the drawer. - */ - private static final int PEEK_DELAY = 160; // ms - - /** - * Minimum velocity that will be detected as a fling - */ - private static final int MIN_FLING_VELOCITY = 400; // dips per second - - /** - * Experimental feature. - */ - private static final boolean ALLOW_EDGE_LOCK = false; - - private static final boolean CHILDREN_DISALLOW_INTERCEPT = true; - - private static final float TOUCH_SLOP_SENSITIVITY = 1.f; - - private static final int[] LAYOUT_ATTRS = new int[] { - android.R.attr.layout_gravity - }; - - /** Whether we can use NO_HIDE_DESCENDANTS accessibility importance. */ - private static final boolean CAN_HIDE_DESCENDANTS = Build.VERSION.SDK_INT >= 19; - - /** Whether the drawer shadow comes from setting elevation on the drawer. */ - private static final boolean SET_DRAWER_SHADOW_FROM_ELEVATION = - Build.VERSION.SDK_INT >= 21; - - private final ChildAccessibilityDelegate mChildAccessibilityDelegate = - new ChildAccessibilityDelegate(); - private float mDrawerElevation; - - private int mMinDrawerMargin; - - private int mScrimColor = DEFAULT_SCRIM_COLOR; - private float mScrimOpacity; - private Paint mScrimPaint = new Paint(); - - private final ViewDragHelper mLeftDragger; - private final ViewDragHelper mRightDragger; - private final ViewDragCallback mLeftCallback; - private final ViewDragCallback mRightCallback; - private int mDrawerState; - private boolean mInLayout; - private boolean mFirstLayout = true; - - private @LockMode int mLockModeLeft = LOCK_MODE_UNDEFINED; - private @LockMode int mLockModeRight = LOCK_MODE_UNDEFINED; - private @LockMode int mLockModeStart = LOCK_MODE_UNDEFINED; - private @LockMode int mLockModeEnd = LOCK_MODE_UNDEFINED; - - private boolean mDisallowInterceptRequested; - private boolean mChildrenCanceledTouch; - - private @Nullable - DrawerListener mListener; - private List mListeners; - - private float mInitialMotionX; - private float mInitialMotionY; - - private Drawable mStatusBarBackground; - private Drawable mShadowLeftResolved; - private Drawable mShadowRightResolved; - - private CharSequence mTitleLeft; - private CharSequence mTitleRight; - - private Object mLastInsets; - private boolean mDrawStatusBarBackground; - - /** Shadow drawables for different gravity */ - private Drawable mShadowStart = null; - private Drawable mShadowEnd = null; - private Drawable mShadowLeft = null; - private Drawable mShadowRight = null; - - private final ArrayList mNonDrawerViews; - - /** - * Listener for monitoring events about drawers. - */ - public interface DrawerListener { - /** - * Called when a drawer's position changes. - * @param drawerView The child view that was moved - * @param slideOffset The new offset of this drawer within its range, from 0-1 - */ - public void onDrawerSlide(View drawerView, float slideOffset); - - /** - * Called when a drawer has settled in a completely open state. - * The drawer is interactive at this point. - * - * @param drawerView Drawer view that is now open - */ - public void onDrawerOpened(View drawerView); - - /** - * Called when a drawer has settled in a completely closed state. - * - * @param drawerView Drawer view that is now closed - */ - public void onDrawerClosed(View drawerView); - - /** - * Called when the drawer motion state changes. The new state will - * be one of {@link #STATE_IDLE}, {@link #STATE_DRAGGING} or {@link #STATE_SETTLING}. - * - * @param newState The new drawer motion state - */ - public void onDrawerStateChanged(@State int newState); - } - - /** - * Stub/no-op implementations of all methods of {@link DrawerListener}. - * Override this if you only care about a few of the available callback methods. - */ - public static abstract class SimpleDrawerListener implements DrawerListener { - @Override - public void onDrawerSlide(View drawerView, float slideOffset) { - } - - @Override - public void onDrawerOpened(View drawerView) { - } - - @Override - public void onDrawerClosed(View drawerView) { - } - - @Override - public void onDrawerStateChanged(int newState) { - } - } - - interface DrawerLayoutCompatImpl { - void configureApplyInsets(View drawerLayout); - void dispatchChildInsets(View child, Object insets, int drawerGravity); - void applyMarginInsets(MarginLayoutParams lp, Object insets, int drawerGravity); - int getTopInset(Object lastInsets); - Drawable getDefaultStatusBarBackground(Context context); - } - - static class DrawerLayoutCompatImplBase implements DrawerLayoutCompatImpl { - public void configureApplyInsets(View drawerLayout) { - // This space for rent - } - - public void dispatchChildInsets(View child, Object insets, int drawerGravity) { - // This space for rent - } - - public void applyMarginInsets(MarginLayoutParams lp, Object insets, int drawerGravity) { - // This space for rent - } - - public int getTopInset(Object insets) { - return 0; - } - - @Override - public Drawable getDefaultStatusBarBackground(Context context) { - return null; - } - } - - static class DrawerLayoutCompatImplApi21 implements DrawerLayoutCompatImpl { - public void configureApplyInsets(View drawerLayout) { - DrawerLayoutCompatApi21.configureApplyInsets(drawerLayout); - } - - public void dispatchChildInsets(View child, Object insets, int drawerGravity) { - DrawerLayoutCompatApi21.dispatchChildInsets(child, insets, drawerGravity); - } - - public void applyMarginInsets(MarginLayoutParams lp, Object insets, int drawerGravity) { - DrawerLayoutCompatApi21.applyMarginInsets(lp, insets, drawerGravity); - } - - public int getTopInset(Object insets) { - return DrawerLayoutCompatApi21.getTopInset(insets); - } - - @Override - public Drawable getDefaultStatusBarBackground(Context context) { - return DrawerLayoutCompatApi21.getDefaultStatusBarBackground(context); - } - } - - static { - final int version = Build.VERSION.SDK_INT; - if (version >= 21) { - IMPL = new DrawerLayoutCompatImplApi21(); - } else { - IMPL = new DrawerLayoutCompatImplBase(); - } - } - - static final DrawerLayoutCompatImpl IMPL; - - public DrawerLayout(Context context) { - this(context, null); - } - - public DrawerLayout(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public DrawerLayout(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS); - final float density = getResources().getDisplayMetrics().density; - mMinDrawerMargin = (int) (MIN_DRAWER_MARGIN * density + 0.5f); - final float minVel = MIN_FLING_VELOCITY * density; - - mLeftCallback = new ViewDragCallback(Gravity.LEFT); - mRightCallback = new ViewDragCallback(Gravity.RIGHT); - - mLeftDragger = ViewDragHelper.create(this, TOUCH_SLOP_SENSITIVITY, mLeftCallback); - mLeftDragger.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT); - mLeftDragger.setMinVelocity(minVel); - mLeftCallback.setDragger(mLeftDragger); - - mRightDragger = ViewDragHelper.create(this, TOUCH_SLOP_SENSITIVITY, mRightCallback); - mRightDragger.setEdgeTrackingEnabled(ViewDragHelper.EDGE_RIGHT); - mRightDragger.setMinVelocity(minVel); - mRightCallback.setDragger(mRightDragger); - - // So that we can catch the back button - setFocusableInTouchMode(true); - - ViewCompat.setImportantForAccessibility(this, - ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES); - - ViewCompat.setAccessibilityDelegate(this, new AccessibilityDelegate()); - ViewGroupCompat.setMotionEventSplittingEnabled(this, false); - if (ViewCompat.getFitsSystemWindows(this)) { - IMPL.configureApplyInsets(this); - mStatusBarBackground = IMPL.getDefaultStatusBarBackground(context); - } - - mDrawerElevation = DRAWER_ELEVATION * density; - - mNonDrawerViews = new ArrayList(); - } - - /** - * Sets the base elevation of the drawer(s) relative to the parent, in pixels. Note that the - * elevation change is only supported in API 21 and above. - * - * @param elevation The base depth position of the view, in pixels. - */ - public void setDrawerElevation(float elevation) { - mDrawerElevation = elevation; - for (int i = 0; i < getChildCount(); i++) { - View child = getChildAt(i); - if (isDrawerView(child)) { - ViewCompat.setElevation(child, mDrawerElevation); - } - } - } - - /** - * The base elevation of the drawer(s) relative to the parent, in pixels. Note that the - * elevation change is only supported in API 21 and above. For unsupported API levels, 0 will - * be returned as the elevation. - * - * @return The base depth position of the view, in pixels. - */ - public float getDrawerElevation() { - if (SET_DRAWER_SHADOW_FROM_ELEVATION) { - return mDrawerElevation; - } - return 0f; - } - - /** - * @hide Internal use only; called to apply window insets when configured - * with fitsSystemWindows="true" - */ - @Override - public void setChildInsets(Object insets, boolean draw) { - mLastInsets = insets; - mDrawStatusBarBackground = draw; - setWillNotDraw(!draw && getBackground() == null); - requestLayout(); - } - - /** - * Set a simple drawable used for the left or right shadow. The drawable provided must have a - * nonzero intrinsic width. For API 21 and above, an elevation will be set on the drawer - * instead of the drawable provided. - * - *

Note that for better support for both left-to-right and right-to-left layout - * directions, a drawable for RTL layout (in additional to the one in LTR layout) can be - * defined with a resource qualifier "ldrtl" for API 17 and above with the gravity - * {@link GravityCompat#START}. Alternatively, for API 23 and above, the drawable can - * auto-mirrored such that the drawable will be mirrored in RTL layout.

- * - * @param shadowDrawable Shadow drawable to use at the edge of a drawer - * @param gravity Which drawer the shadow should apply to - */ - public void setDrawerShadow(Drawable shadowDrawable, @EdgeGravity int gravity) { - /* - * TODO Someone someday might want to set more complex drawables here. - * They're probably nuts, but we might want to consider registering callbacks, - * setting states, etc. properly. - */ - if (SET_DRAWER_SHADOW_FROM_ELEVATION) { - // No op. Drawer shadow will come from setting an elevation on the drawer. - return; - } - if ((gravity & GravityCompat.START) == GravityCompat.START) { - mShadowStart = shadowDrawable; - } else if ((gravity & GravityCompat.END) == GravityCompat.END) { - mShadowEnd = shadowDrawable; - } else if ((gravity & Gravity.LEFT) == Gravity.LEFT) { - mShadowLeft = shadowDrawable; - } else if ((gravity & Gravity.RIGHT) == Gravity.RIGHT) { - mShadowRight = shadowDrawable; - } else { - return; - } - resolveShadowDrawables(); - invalidate(); - } - - /** - * Set a simple drawable used for the left or right shadow. The drawable provided must have a - * nonzero intrinsic width. For API 21 and above, an elevation will be set on the drawer - * instead of the drawable provided. - * - *

Note that for better support for both left-to-right and right-to-left layout - * directions, a drawable for RTL layout (in additional to the one in LTR layout) can be - * defined with a resource qualifier "ldrtl" for API 17 and above with the gravity - * {@link GravityCompat#START}. Alternatively, for API 23 and above, the drawable can - * auto-mirrored such that the drawable will be mirrored in RTL layout.

- * - * @param resId Resource id of a shadow drawable to use at the edge of a drawer - * @param gravity Which drawer the shadow should apply to - */ - public void setDrawerShadow(@DrawableRes int resId, @EdgeGravity int gravity) { - setDrawerShadow(getResources().getDrawable(resId), gravity); - } - - /** - * Set a color to use for the scrim that obscures primary content while a drawer is open. - * - * @param color Color to use in 0xAARRGGBB format. - */ - public void setScrimColor(@ColorInt int color) { - mScrimColor = color; - invalidate(); - } - - /** - * Set a listener to be notified of drawer events. Note that this method is deprecated - * and you should use {@link #addDrawerListener(DrawerListener)} to add a listener and - * {@link #removeDrawerListener(DrawerListener)} to remove a registered listener. - * - * @param listener Listener to notify when drawer events occur - * @deprecated Use {@link #addDrawerListener(DrawerListener)} - * @see DrawerListener - * @see #addDrawerListener(DrawerListener) - * @see #removeDrawerListener(DrawerListener) - */ - @Deprecated - public void setDrawerListener(DrawerListener listener) { - // The logic in this method emulates what we had before support for multiple - // registered listeners. - if (mListener != null) { - removeDrawerListener(mListener); - } - if (listener != null) { - addDrawerListener(listener); - } - // Update the deprecated field so that we can remove the passed listener the next - // time we're called - mListener = listener; - } - - /** - * Adds the specified listener to the list of listeners that will be notified of drawer events. - * - * @param listener Listener to notify when drawer events occur. - * @see #removeDrawerListener(DrawerListener) - */ - public void addDrawerListener(@NonNull DrawerListener listener) { - if (listener == null) { - return; - } - if (mListeners == null) { - mListeners = new ArrayList(); - } - mListeners.add(listener); - } - - /** - * Removes the specified listener from the list of listeners that will be notified of drawer - * events. - * - * @param listener Listener to remove from being notified of drawer events - * @see #addDrawerListener(DrawerListener) - */ - public void removeDrawerListener(@NonNull DrawerListener listener) { - if (listener == null) { - return; - } - if (mListeners == null) { - // This can happen if this method is called before the first call to addDrawerListener - return; - } - mListeners.remove(listener); - } - - /** - * Enable or disable interaction with all drawers. - * - *

This allows the application to restrict the user's ability to open or close - * any drawer within this layout. DrawerLayout will still respond to calls to - * {@link #openDrawer(int)}, {@link #closeDrawer(int)} and friends if a drawer is locked.

- * - *

Locking drawers open or closed will implicitly open or close - * any drawers as appropriate.

- * - * @param lockMode The new lock mode for the given drawer. One of {@link #LOCK_MODE_UNLOCKED}, - * {@link #LOCK_MODE_LOCKED_CLOSED} or {@link #LOCK_MODE_LOCKED_OPEN}. - */ - public void setDrawerLockMode(@LockMode int lockMode) { - setDrawerLockMode(lockMode, Gravity.LEFT); - setDrawerLockMode(lockMode, Gravity.RIGHT); - } - - /** - * Enable or disable interaction with the given drawer. - * - *

This allows the application to restrict the user's ability to open or close - * the given drawer. DrawerLayout will still respond to calls to {@link #openDrawer(int)}, - * {@link #closeDrawer(int)} and friends if a drawer is locked.

- * - *

Locking a drawer open or closed will implicitly open or close - * that drawer as appropriate.

- * - * @param lockMode The new lock mode for the given drawer. One of {@link #LOCK_MODE_UNLOCKED}, - * {@link #LOCK_MODE_LOCKED_CLOSED} or {@link #LOCK_MODE_LOCKED_OPEN}. - * @param edgeGravity Gravity.LEFT, RIGHT, START or END. - * Expresses which drawer to change the mode for. - * - * @see #LOCK_MODE_UNLOCKED - * @see #LOCK_MODE_LOCKED_CLOSED - * @see #LOCK_MODE_LOCKED_OPEN - */ - public void setDrawerLockMode(@LockMode int lockMode, @EdgeGravity int edgeGravity) { - final int absGravity = GravityCompat.getAbsoluteGravity(edgeGravity, - ViewCompat.getLayoutDirection(this)); - - switch (edgeGravity) { - case Gravity.LEFT: - mLockModeLeft = lockMode; - break; - case Gravity.RIGHT: - mLockModeRight = lockMode; - break; - case GravityCompat.START: - mLockModeStart = lockMode; - break; - case GravityCompat.END: - mLockModeEnd = lockMode; - break; - } - - if (lockMode != LOCK_MODE_UNLOCKED) { - // Cancel interaction in progress - final ViewDragHelper helper = absGravity == Gravity.LEFT ? mLeftDragger : mRightDragger; - helper.cancel(); - } - switch (lockMode) { - case LOCK_MODE_LOCKED_OPEN: - final View toOpen = findDrawerWithGravity(absGravity); - if (toOpen != null) { - openDrawer(toOpen); - } - break; - case LOCK_MODE_LOCKED_CLOSED: - final View toClose = findDrawerWithGravity(absGravity); - if (toClose != null) { - closeDrawer(toClose); - } - break; - // default: do nothing - } - } - - /** - * Enable or disable interaction with the given drawer. - * - *

This allows the application to restrict the user's ability to open or close - * the given drawer. DrawerLayout will still respond to calls to {@link #openDrawer(int)}, - * {@link #closeDrawer(int)} and friends if a drawer is locked.

- * - *

Locking a drawer open or closed will implicitly open or close - * that drawer as appropriate.

- * - * @param lockMode The new lock mode for the given drawer. One of {@link #LOCK_MODE_UNLOCKED}, - * {@link #LOCK_MODE_LOCKED_CLOSED} or {@link #LOCK_MODE_LOCKED_OPEN}. - * @param drawerView The drawer view to change the lock mode for - * - * @see #LOCK_MODE_UNLOCKED - * @see #LOCK_MODE_LOCKED_CLOSED - * @see #LOCK_MODE_LOCKED_OPEN - */ - public void setDrawerLockMode(@LockMode int lockMode, View drawerView) { - if (!isDrawerView(drawerView)) { - throw new IllegalArgumentException("View " + drawerView + " is not a " + - "drawer with appropriate layout_gravity"); - } - final int gravity = ((LayoutParams) drawerView.getLayoutParams()).gravity; - setDrawerLockMode(lockMode, gravity); - } - - /** - * Check the lock mode of the drawer with the given gravity. - * - * @param edgeGravity Gravity of the drawer to check - * @return one of {@link #LOCK_MODE_UNLOCKED}, {@link #LOCK_MODE_LOCKED_CLOSED} or - * {@link #LOCK_MODE_LOCKED_OPEN}. - */ - @LockMode - public int getDrawerLockMode(@EdgeGravity int edgeGravity) { - int layoutDirection = ViewCompat.getLayoutDirection(this); - - switch (edgeGravity) { - case Gravity.LEFT: - if (mLockModeLeft != LOCK_MODE_UNDEFINED) { - return mLockModeLeft; - } - int leftLockMode = (layoutDirection == ViewCompat.LAYOUT_DIRECTION_LTR) ? - mLockModeStart : mLockModeEnd; - if (leftLockMode != LOCK_MODE_UNDEFINED) { - return leftLockMode; - } - break; - case Gravity.RIGHT: - if (mLockModeRight != LOCK_MODE_UNDEFINED) { - return mLockModeRight; - } - int rightLockMode = (layoutDirection == ViewCompat.LAYOUT_DIRECTION_LTR) ? - mLockModeEnd : mLockModeStart; - if (rightLockMode != LOCK_MODE_UNDEFINED) { - return rightLockMode; - } - break; - case GravityCompat.START: - if (mLockModeStart != LOCK_MODE_UNDEFINED) { - return mLockModeStart; - } - int startLockMode = (layoutDirection == ViewCompat.LAYOUT_DIRECTION_LTR) ? - mLockModeLeft : mLockModeRight; - if (startLockMode != LOCK_MODE_UNDEFINED) { - return startLockMode; - } - break; - case GravityCompat.END: - if (mLockModeEnd != LOCK_MODE_UNDEFINED) { - return mLockModeEnd; - } - int endLockMode = (layoutDirection == ViewCompat.LAYOUT_DIRECTION_LTR) ? - mLockModeRight : mLockModeLeft; - if (endLockMode != LOCK_MODE_UNDEFINED) { - return endLockMode; - } - break; - } - - return LOCK_MODE_UNLOCKED; - } - - /** - * Check the lock mode of the given drawer view. - * - * @param drawerView Drawer view to check lock mode - * @return one of {@link #LOCK_MODE_UNLOCKED}, {@link #LOCK_MODE_LOCKED_CLOSED} or - * {@link #LOCK_MODE_LOCKED_OPEN}. - */ - @LockMode - public int getDrawerLockMode(View drawerView) { - if (!isDrawerView(drawerView)) { - throw new IllegalArgumentException("View " + drawerView + " is not a drawer"); - } - final int drawerGravity = ((LayoutParams) drawerView.getLayoutParams()).gravity; - return getDrawerLockMode(drawerGravity); - } - - /** - * Sets the title of the drawer with the given gravity. - *

- * When accessibility is turned on, this is the title that will be used to - * identify the drawer to the active accessibility service. - * - * @param edgeGravity Gravity.LEFT, RIGHT, START or END. Expresses which - * drawer to set the title for. - * @param title The title for the drawer. - */ - public void setDrawerTitle(@EdgeGravity int edgeGravity, CharSequence title) { - final int absGravity = GravityCompat.getAbsoluteGravity( - edgeGravity, ViewCompat.getLayoutDirection(this)); - if (absGravity == Gravity.LEFT) { - mTitleLeft = title; - } else if (absGravity == Gravity.RIGHT) { - mTitleRight = title; - } - } - - /** - * Returns the title of the drawer with the given gravity. - * - * @param edgeGravity Gravity.LEFT, RIGHT, START or END. Expresses which - * drawer to return the title for. - * @return The title of the drawer, or null if none set. - * @see #setDrawerTitle(int, CharSequence) - */ - @Nullable - public CharSequence getDrawerTitle(@EdgeGravity int edgeGravity) { - final int absGravity = GravityCompat.getAbsoluteGravity( - edgeGravity, ViewCompat.getLayoutDirection(this)); - if (absGravity == Gravity.LEFT) { - return mTitleLeft; - } else if (absGravity == Gravity.RIGHT) { - return mTitleRight; - } - return null; - } - - /** - * Resolve the shared state of all drawers from the component ViewDragHelpers. - * Should be called whenever a ViewDragHelper's state changes. - */ - void updateDrawerState(int forGravity, @State int activeState, View activeDrawer) { - final int leftState = mLeftDragger.getViewDragState(); - final int rightState = mRightDragger.getViewDragState(); - - final int state; - if (leftState == STATE_DRAGGING || rightState == STATE_DRAGGING) { - state = STATE_DRAGGING; - } else if (leftState == STATE_SETTLING || rightState == STATE_SETTLING) { - state = STATE_SETTLING; - } else { - state = STATE_IDLE; - } - - if (activeDrawer != null && activeState == STATE_IDLE) { - final LayoutParams lp = (LayoutParams) activeDrawer.getLayoutParams(); - if (lp.onScreen == 0) { - dispatchOnDrawerClosed(activeDrawer); - } else if (lp.onScreen == 1) { - dispatchOnDrawerOpened(activeDrawer); - } - } - - if (state != mDrawerState) { - mDrawerState = state; - - if (mListeners != null) { - // Notify the listeners. Do that from the end of the list so that if a listener - // removes itself as the result of being called, it won't mess up with our iteration - int listenerCount = mListeners.size(); - for (int i = listenerCount - 1; i >= 0; i--) { - mListeners.get(i).onDrawerStateChanged(state); - } - } - } - } - - void dispatchOnDrawerClosed(View drawerView) { - final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams(); - if ((lp.openState & LayoutParams.FLAG_IS_OPENED) == 1) { - lp.openState = 0; - - if (mListeners != null) { - // Notify the listeners. Do that from the end of the list so that if a listener - // removes itself as the result of being called, it won't mess up with our iteration - int listenerCount = mListeners.size(); - for (int i = listenerCount - 1; i >= 0; i--) { - mListeners.get(i).onDrawerClosed(drawerView); - } - } - - updateChildrenImportantForAccessibility(drawerView, false); - - // Only send WINDOW_STATE_CHANGE if the host has window focus. This - // may change if support for multiple foreground windows (e.g. IME) - // improves. - if (hasWindowFocus()) { - final View rootView = getRootView(); - if (rootView != null) { - rootView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); - } - } - } - } - - void dispatchOnDrawerOpened(View drawerView) { - final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams(); - if ((lp.openState & LayoutParams.FLAG_IS_OPENED) == 0) { - lp.openState = LayoutParams.FLAG_IS_OPENED; - if (mListeners != null) { - // Notify the listeners. Do that from the end of the list so that if a listener - // removes itself as the result of being called, it won't mess up with our iteration - int listenerCount = mListeners.size(); - for (int i = listenerCount - 1; i >= 0; i--) { - mListeners.get(i).onDrawerOpened(drawerView); - } - } - - updateChildrenImportantForAccessibility(drawerView, true); - - // Only send WINDOW_STATE_CHANGE if the host has window focus. - if (hasWindowFocus()) { - sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED); - } - - drawerView.requestFocus(); - } - } - - private void updateChildrenImportantForAccessibility(View drawerView, boolean isDrawerOpen) { - final int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - final View child = getChildAt(i); - if (!isDrawerOpen && !isDrawerView(child) - || isDrawerOpen && child == drawerView) { - // Drawer is closed and this is a content view or this is an - // open drawer view, so it should be visible. - ViewCompat.setImportantForAccessibility(child, - ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES); - } else { - ViewCompat.setImportantForAccessibility(child, - ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS); - } - } - } - - void dispatchOnDrawerSlide(View drawerView, float slideOffset) { - if (mListeners != null) { - // Notify the listeners. Do that from the end of the list so that if a listener - // removes itself as the result of being called, it won't mess up with our iteration - int listenerCount = mListeners.size(); - for (int i = listenerCount - 1; i >= 0; i--) { - mListeners.get(i).onDrawerSlide(drawerView, slideOffset); - } - } - } - - void setDrawerViewOffset(View drawerView, float slideOffset) { - final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams(); - if (slideOffset == lp.onScreen) { - return; - } - - lp.onScreen = slideOffset; - dispatchOnDrawerSlide(drawerView, slideOffset); - } - - float getDrawerViewOffset(View drawerView) { - return ((LayoutParams) drawerView.getLayoutParams()).onScreen; - } - - /** - * @return the absolute gravity of the child drawerView, resolved according - * to the current layout direction - */ - int getDrawerViewAbsoluteGravity(View drawerView) { - final int gravity = ((LayoutParams) drawerView.getLayoutParams()).gravity; - return GravityCompat.getAbsoluteGravity(gravity, ViewCompat.getLayoutDirection(this)); - } - - boolean checkDrawerViewAbsoluteGravity(View drawerView, int checkFor) { - final int absGravity = getDrawerViewAbsoluteGravity(drawerView); - return (absGravity & checkFor) == checkFor; - } - - View findOpenDrawer() { - final int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - final View child = getChildAt(i); - final LayoutParams childLp = (LayoutParams) child.getLayoutParams(); - if ((childLp.openState & LayoutParams.FLAG_IS_OPENED) == 1) { - return child; - } - } - return null; - } - - void moveDrawerToOffset(View drawerView, float slideOffset) { - final float oldOffset = getDrawerViewOffset(drawerView); - final int width = drawerView.getWidth(); - final int oldPos = (int) (width * oldOffset); - final int newPos = (int) (width * slideOffset); - final int dx = newPos - oldPos; - - drawerView.offsetLeftAndRight( - checkDrawerViewAbsoluteGravity(drawerView, Gravity.LEFT) ? dx : -dx); - setDrawerViewOffset(drawerView, slideOffset); - } - - /** - * @param gravity the gravity of the child to return. If specified as a - * relative value, it will be resolved according to the current - * layout direction. - * @return the drawer with the specified gravity - */ - View findDrawerWithGravity(int gravity) { - final int absHorizGravity = GravityCompat.getAbsoluteGravity( - gravity, ViewCompat.getLayoutDirection(this)) & Gravity.HORIZONTAL_GRAVITY_MASK; - final int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - final View child = getChildAt(i); - final int childAbsGravity = getDrawerViewAbsoluteGravity(child); - if ((childAbsGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == absHorizGravity) { - return child; - } - } - return null; - } - - /** - * Simple gravity to string - only supports LEFT and RIGHT for debugging output. - * - * @param gravity Absolute gravity value - * @return LEFT or RIGHT as appropriate, or a hex string - */ - static String gravityToString(@EdgeGravity int gravity) { - if ((gravity & Gravity.LEFT) == Gravity.LEFT) { - return "LEFT"; - } - if ((gravity & Gravity.RIGHT) == Gravity.RIGHT) { - return "RIGHT"; - } - return Integer.toHexString(gravity); - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - mFirstLayout = true; - } - - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - mFirstLayout = true; - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - int widthMode = MeasureSpec.getMode(widthMeasureSpec); - int heightMode = MeasureSpec.getMode(heightMeasureSpec); - int widthSize = MeasureSpec.getSize(widthMeasureSpec); - int heightSize = MeasureSpec.getSize(heightMeasureSpec); - - if (widthMode != MeasureSpec.EXACTLY || heightMode != MeasureSpec.EXACTLY) { - if (isInEditMode()) { - // Don't crash the layout editor. Consume all of the space if specified - // or pick a magic number from thin air otherwise. - // TODO Better communication with tools of this bogus state. - // It will crash on a real device. - if (widthMode == MeasureSpec.AT_MOST) { - widthMode = MeasureSpec.EXACTLY; - } else if (widthMode == MeasureSpec.UNSPECIFIED) { - widthMode = MeasureSpec.EXACTLY; - widthSize = 300; - } - if (heightMode == MeasureSpec.AT_MOST) { - heightMode = MeasureSpec.EXACTLY; - } - else if (heightMode == MeasureSpec.UNSPECIFIED) { - heightMode = MeasureSpec.EXACTLY; - heightSize = 300; - } - } else { - throw new IllegalArgumentException( - "DrawerLayout must be measured with MeasureSpec.EXACTLY."); - } - } - - setMeasuredDimension(widthSize, heightSize); - - final boolean applyInsets = mLastInsets != null && ViewCompat.getFitsSystemWindows(this); - final int layoutDirection = ViewCompat.getLayoutDirection(this); - - // Only one drawer is permitted along each vertical edge (left / right). These two booleans - // are tracking the presence of the edge drawers. - boolean hasDrawerOnLeftEdge = false; - boolean hasDrawerOnRightEdge = false; - final int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - final View child = getChildAt(i); - - if (child.getVisibility() == GONE) { - continue; - } - - final LayoutParams lp = (LayoutParams) child.getLayoutParams(); - - if (applyInsets) { - final int cgrav = GravityCompat.getAbsoluteGravity(lp.gravity, layoutDirection); - if (ViewCompat.getFitsSystemWindows(child)) { - IMPL.dispatchChildInsets(child, mLastInsets, cgrav); - } else { - IMPL.applyMarginInsets(lp, mLastInsets, cgrav); - } - } - - if (isContentView(child)) { - // Content views get measured at exactly the layout's size. - final int contentWidthSpec = MeasureSpec.makeMeasureSpec( - widthSize - lp.leftMargin - lp.rightMargin, MeasureSpec.EXACTLY); - final int contentHeightSpec = MeasureSpec.makeMeasureSpec( - heightSize - lp.topMargin - lp.bottomMargin, MeasureSpec.EXACTLY); - child.measure(contentWidthSpec, contentHeightSpec); - } else if (isDrawerView(child)) { - if (SET_DRAWER_SHADOW_FROM_ELEVATION) { - if (ViewCompat.getElevation(child) != mDrawerElevation) { - ViewCompat.setElevation(child, mDrawerElevation); - } - } - final @EdgeGravity int childGravity = - getDrawerViewAbsoluteGravity(child) & Gravity.HORIZONTAL_GRAVITY_MASK; - // Note that the isDrawerView check guarantees that childGravity here is either - // LEFT or RIGHT - boolean isLeftEdgeDrawer = (childGravity == Gravity.LEFT); - if ((isLeftEdgeDrawer && hasDrawerOnLeftEdge) || - (!isLeftEdgeDrawer && hasDrawerOnRightEdge)) { - throw new IllegalStateException("Child drawer has absolute gravity " + - gravityToString(childGravity) + " but this " + TAG + " already has a " + - "drawer view along that edge"); - } - if (isLeftEdgeDrawer) { - hasDrawerOnLeftEdge = true; - } else { - hasDrawerOnRightEdge = true; - } - final int drawerWidthSpec = getChildMeasureSpec(widthMeasureSpec, - mMinDrawerMargin + lp.leftMargin + lp.rightMargin, - lp.width); - final int drawerHeightSpec = getChildMeasureSpec(heightMeasureSpec, - lp.topMargin + lp.bottomMargin, - lp.height); - child.measure(drawerWidthSpec, drawerHeightSpec); - } else { - throw new IllegalStateException("Child " + child + " at index " + i + - " does not have a valid layout_gravity - must be Gravity.LEFT, " + - "Gravity.RIGHT or Gravity.NO_GRAVITY"); - } - } - } - - private void resolveShadowDrawables() { - if (SET_DRAWER_SHADOW_FROM_ELEVATION) { - return; - } - mShadowLeftResolved = resolveLeftShadow(); - mShadowRightResolved = resolveRightShadow(); - } - - private Drawable resolveLeftShadow() { - int layoutDirection = ViewCompat.getLayoutDirection(this); - // Prefer shadows defined with start/end gravity over left and right. - if (layoutDirection == ViewCompat.LAYOUT_DIRECTION_LTR) { - if (mShadowStart != null) { - // Correct drawable layout direction, if needed. - mirror(mShadowStart, layoutDirection); - return mShadowStart; - } - } else { - if (mShadowEnd != null) { - // Correct drawable layout direction, if needed. - mirror(mShadowEnd, layoutDirection); - return mShadowEnd; - } - } - return mShadowLeft; - } - - private Drawable resolveRightShadow() { - int layoutDirection = ViewCompat.getLayoutDirection(this); - if (layoutDirection == ViewCompat.LAYOUT_DIRECTION_LTR) { - if (mShadowEnd != null) { - // Correct drawable layout direction, if needed. - mirror(mShadowEnd, layoutDirection); - return mShadowEnd; - } - } else { - if (mShadowStart != null) { - // Correct drawable layout direction, if needed. - mirror(mShadowStart, layoutDirection); - return mShadowStart; - } - } - return mShadowRight; - } - - /** - * Change the layout direction of the given drawable. - * Return true if auto-mirror is supported and drawable's layout direction can be changed. - * Otherwise, return false. - */ - private boolean mirror(Drawable drawable, int layoutDirection) { - if (drawable == null || !DrawableCompat.isAutoMirrored(drawable)) { - return false; - } - - DrawableCompat.setLayoutDirection(drawable, layoutDirection); - return true; - } - - @Override - protected void onLayout(boolean changed, int l, int t, int r, int b) { - mInLayout = true; - final int width = r - l; - final int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - final View child = getChildAt(i); - - if (child.getVisibility() == GONE) { - continue; - } - - final LayoutParams lp = (LayoutParams) child.getLayoutParams(); - - if (isContentView(child)) { - child.layout(lp.leftMargin, lp.topMargin, - lp.leftMargin + child.getMeasuredWidth(), - lp.topMargin + child.getMeasuredHeight()); - } else { // Drawer, if it wasn't onMeasure would have thrown an exception. - final int childWidth = child.getMeasuredWidth(); - final int childHeight = child.getMeasuredHeight(); - int childLeft; - - final float newOffset; - if (checkDrawerViewAbsoluteGravity(child, Gravity.LEFT)) { - childLeft = -childWidth + (int) (childWidth * lp.onScreen); - newOffset = (float) (childWidth + childLeft) / childWidth; - } else { // Right; onMeasure checked for us. - childLeft = width - (int) (childWidth * lp.onScreen); - newOffset = (float) (width - childLeft) / childWidth; - } - - final boolean changeOffset = newOffset != lp.onScreen; - - final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK; - - switch (vgrav) { - default: - case Gravity.TOP: { - child.layout(childLeft, lp.topMargin, childLeft + childWidth, - lp.topMargin + childHeight); - break; - } - - case Gravity.BOTTOM: { - final int height = b - t; - child.layout(childLeft, - height - lp.bottomMargin - child.getMeasuredHeight(), - childLeft + childWidth, - height - lp.bottomMargin); - break; - } - - case Gravity.CENTER_VERTICAL: { - final int height = b - t; - int childTop = (height - childHeight) / 2; - - // Offset for margins. If things don't fit right because of - // bad measurement before, oh well. - if (childTop < lp.topMargin) { - childTop = lp.topMargin; - } else if (childTop + childHeight > height - lp.bottomMargin) { - childTop = height - lp.bottomMargin - childHeight; - } - child.layout(childLeft, childTop, childLeft + childWidth, - childTop + childHeight); - break; - } - } - - if (changeOffset) { - setDrawerViewOffset(child, newOffset); - } - - final int newVisibility = lp.onScreen > 0 ? VISIBLE : INVISIBLE; - if (child.getVisibility() != newVisibility) { - child.setVisibility(newVisibility); - } - } - } - mInLayout = false; - mFirstLayout = false; - } - - @Override - public void requestLayout() { - if (!mInLayout) { - super.requestLayout(); - } - } - - @Override - public void computeScroll() { - final int childCount = getChildCount(); - float scrimOpacity = 0; - for (int i = 0; i < childCount; i++) { - final float onscreen = ((LayoutParams) getChildAt(i).getLayoutParams()).onScreen; - scrimOpacity = Math.max(scrimOpacity, onscreen); - } - mScrimOpacity = scrimOpacity; - - // "|" used on purpose; both need to run. - if (mLeftDragger.continueSettling(true) | mRightDragger.continueSettling(true)) { - ViewCompat.postInvalidateOnAnimation(this); - } - } - - private static boolean hasOpaqueBackground(View v) { - final Drawable bg = v.getBackground(); - if (bg != null) { - return bg.getOpacity() == PixelFormat.OPAQUE; - } - return false; - } - - /** - * Set a drawable to draw in the insets area for the status bar. - * Note that this will only be activated if this DrawerLayout fitsSystemWindows. - * - * @param bg Background drawable to draw behind the status bar - */ - public void setStatusBarBackground(Drawable bg) { - mStatusBarBackground = bg; - invalidate(); - } - - /** - * Gets the drawable used to draw in the insets area for the status bar. - * - * @return The status bar background drawable, or null if none set - */ - public Drawable getStatusBarBackgroundDrawable() { - return mStatusBarBackground; - } - - /** - * Set a drawable to draw in the insets area for the status bar. - * Note that this will only be activated if this DrawerLayout fitsSystemWindows. - * - * @param resId Resource id of a background drawable to draw behind the status bar - */ - public void setStatusBarBackground(int resId) { - mStatusBarBackground = resId != 0 ? ContextCompat.getDrawable(getContext(), resId) : null; - invalidate(); - } - - /** - * Set a drawable to draw in the insets area for the status bar. - * Note that this will only be activated if this DrawerLayout fitsSystemWindows. - * - * @param color Color to use as a background drawable to draw behind the status bar - * in 0xAARRGGBB format. - */ - public void setStatusBarBackgroundColor(@ColorInt int color) { - mStatusBarBackground = new ColorDrawable(color); - invalidate(); - } - - public void onRtlPropertiesChanged(int layoutDirection) { - resolveShadowDrawables(); - } - - @Override - public void onDraw(Canvas c) { - super.onDraw(c); - if (mDrawStatusBarBackground && mStatusBarBackground != null) { - final int inset = IMPL.getTopInset(mLastInsets); - if (inset > 0) { - mStatusBarBackground.setBounds(0, 0, getWidth(), inset); - mStatusBarBackground.draw(c); - } - } - } - - @Override - protected boolean drawChild(Canvas canvas, View child, long drawingTime) { - final int height = getHeight(); - final boolean drawingContent = isContentView(child); - int clipLeft = 0, clipRight = getWidth(); - - final int restoreCount = canvas.save(); - if (drawingContent) { - final int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - final View v = getChildAt(i); - if (v == child || v.getVisibility() != VISIBLE || - !hasOpaqueBackground(v) || !isDrawerView(v) || - v.getHeight() < height) { - continue; - } - - if (checkDrawerViewAbsoluteGravity(v, Gravity.LEFT)) { - final int vright = v.getRight(); - if (vright > clipLeft) clipLeft = vright; - } else { - final int vleft = v.getLeft(); - if (vleft < clipRight) clipRight = vleft; - } - } - canvas.clipRect(clipLeft, 0, clipRight, getHeight()); - } - final boolean result = super.drawChild(canvas, child, drawingTime); - canvas.restoreToCount(restoreCount); - - if (mScrimOpacity > 0 && drawingContent) { - final int baseAlpha = (mScrimColor & 0xff000000) >>> 24; - final int imag = (int) (baseAlpha * mScrimOpacity); - final int color = imag << 24 | (mScrimColor & 0xffffff); - mScrimPaint.setColor(color); - - canvas.drawRect(clipLeft, 0, clipRight, getHeight(), mScrimPaint); - } else if (mShadowLeftResolved != null - && checkDrawerViewAbsoluteGravity(child, Gravity.LEFT)) { - final int shadowWidth = mShadowLeftResolved.getIntrinsicWidth(); - final int childRight = child.getRight(); - final int drawerPeekDistance = mLeftDragger.getEdgeSize(); - final float alpha = - Math.max(0, Math.min((float) childRight / drawerPeekDistance, 1.f)); - mShadowLeftResolved.setBounds(childRight, child.getTop(), - childRight + shadowWidth, child.getBottom()); - mShadowLeftResolved.setAlpha((int) (0xff * alpha)); - mShadowLeftResolved.draw(canvas); - } else if (mShadowRightResolved != null - && checkDrawerViewAbsoluteGravity(child, Gravity.RIGHT)) { - final int shadowWidth = mShadowRightResolved.getIntrinsicWidth(); - final int childLeft = child.getLeft(); - final int showing = getWidth() - childLeft; - final int drawerPeekDistance = mRightDragger.getEdgeSize(); - final float alpha = - Math.max(0, Math.min((float) showing / drawerPeekDistance, 1.f)); - mShadowRightResolved.setBounds(childLeft - shadowWidth, child.getTop(), - childLeft, child.getBottom()); - mShadowRightResolved.setAlpha((int) (0xff * alpha)); - mShadowRightResolved.draw(canvas); - } - return result; - } - - boolean isContentView(View child) { - return ((LayoutParams) child.getLayoutParams()).gravity == Gravity.NO_GRAVITY; - } - - boolean isDrawerView(View child) { - final int gravity = ((LayoutParams) child.getLayoutParams()).gravity; - final int absGravity = GravityCompat.getAbsoluteGravity(gravity, - ViewCompat.getLayoutDirection(child)); - if ((absGravity & Gravity.LEFT) != 0) { - // This child is a left-edge drawer - return true; - } - if ((absGravity & Gravity.RIGHT) != 0) { - // This child is a right-edge drawer - return true; - } - return false; - } - - @Override - public boolean onInterceptTouchEvent(MotionEvent ev) { - final int action = MotionEventCompat.getActionMasked(ev); - - // "|" used deliberately here; both methods should be invoked. - final boolean interceptForDrag = mLeftDragger.shouldInterceptTouchEvent(ev) | - mRightDragger.shouldInterceptTouchEvent(ev); - - boolean interceptForTap = false; - - switch (action) { - case MotionEvent.ACTION_DOWN: { - final float x = ev.getX(); - final float y = ev.getY(); - mInitialMotionX = x; - mInitialMotionY = y; - if (mScrimOpacity > 0) { - final View child = mLeftDragger.findTopChildUnder((int) x, (int) y); - if (child != null && isContentView(child)) { - interceptForTap = true; - } - } - mDisallowInterceptRequested = false; - mChildrenCanceledTouch = false; - break; - } - - case MotionEvent.ACTION_MOVE: { - // If we cross the touch slop, don't perform the delayed peek for an edge touch. - if (mLeftDragger.checkTouchSlop(ViewDragHelper.DIRECTION_ALL)) { - mLeftCallback.removeCallbacks(); - mRightCallback.removeCallbacks(); - } - break; - } - - case MotionEvent.ACTION_CANCEL: - case MotionEvent.ACTION_UP: { - closeDrawers(true); - mDisallowInterceptRequested = false; - mChildrenCanceledTouch = false; - } - } - - return interceptForDrag || interceptForTap || hasPeekingDrawer() || mChildrenCanceledTouch; - } - - @Override - public boolean onTouchEvent(MotionEvent ev) { - mLeftDragger.processTouchEvent(ev); - mRightDragger.processTouchEvent(ev); - - final int action = ev.getAction(); - boolean wantTouchEvents = true; - - switch (action & MotionEventCompat.ACTION_MASK) { - case MotionEvent.ACTION_DOWN: { - final float x = ev.getX(); - final float y = ev.getY(); - mInitialMotionX = x; - mInitialMotionY = y; - mDisallowInterceptRequested = false; - mChildrenCanceledTouch = false; - break; - } - - case MotionEvent.ACTION_UP: { - final float x = ev.getX(); - final float y = ev.getY(); - boolean peekingOnly = true; - final View touchedView = mLeftDragger.findTopChildUnder((int) x, (int) y); - if (touchedView != null && isContentView(touchedView)) { - final float dx = x - mInitialMotionX; - final float dy = y - mInitialMotionY; - final int slop = mLeftDragger.getTouchSlop(); - if (dx * dx + dy * dy < slop * slop) { - // Taps close a dimmed open drawer but only if it isn't locked open. - final View openDrawer = findOpenDrawer(); - if (openDrawer != null) { - peekingOnly = getDrawerLockMode(openDrawer) == LOCK_MODE_LOCKED_OPEN; - } - } - } - closeDrawers(peekingOnly); - mDisallowInterceptRequested = false; - break; - } - - case MotionEvent.ACTION_CANCEL: { - closeDrawers(true); - mDisallowInterceptRequested = false; - mChildrenCanceledTouch = false; - break; - } - } - - return wantTouchEvents; - } - - public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { - if (CHILDREN_DISALLOW_INTERCEPT || - (!mLeftDragger.isEdgeTouched(ViewDragHelper.EDGE_LEFT) && - !mRightDragger.isEdgeTouched(ViewDragHelper.EDGE_RIGHT))) { - // If we have an edge touch we want to skip this and track it for later instead. - super.requestDisallowInterceptTouchEvent(disallowIntercept); - } - mDisallowInterceptRequested = disallowIntercept; - if (disallowIntercept) { - closeDrawers(true); - } - } - - /** - * Close all currently open drawer views by animating them out of view. - */ - public void closeDrawers() { - closeDrawers(false); - } - - void closeDrawers(boolean peekingOnly) { - boolean needsInvalidate = false; - final int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - final View child = getChildAt(i); - final LayoutParams lp = (LayoutParams) child.getLayoutParams(); - - if (!isDrawerView(child) || (peekingOnly && !lp.isPeeking)) { - continue; - } - - final int childWidth = child.getWidth(); - - if (checkDrawerViewAbsoluteGravity(child, Gravity.LEFT)) { - needsInvalidate |= mLeftDragger.smoothSlideViewTo(child, - -childWidth, child.getTop()); - } else { - needsInvalidate |= mRightDragger.smoothSlideViewTo(child, - getWidth(), child.getTop()); - } - - lp.isPeeking = false; - } - - mLeftCallback.removeCallbacks(); - mRightCallback.removeCallbacks(); - - if (needsInvalidate) { - invalidate(); - } - } - - /** - * Open the specified drawer view by animating it into view. - * - * @param drawerView Drawer view to open - */ - public void openDrawer(View drawerView) { - openDrawer(drawerView, true); - } - - /** - * Open the specified drawer view. - * - * @param drawerView Drawer view to open - * @param animate Whether opening of the drawer should be animated. - */ - public void openDrawer(View drawerView, boolean animate) { - if (!isDrawerView(drawerView)) { - throw new IllegalArgumentException("View " + drawerView + " is not a sliding drawer"); - } - - final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams(); - if (mFirstLayout) { - lp.onScreen = 1.f; - lp.openState = LayoutParams.FLAG_IS_OPENED; - - updateChildrenImportantForAccessibility(drawerView, true); - } else if (animate) { - lp.openState |= LayoutParams.FLAG_IS_OPENING; - - if (checkDrawerViewAbsoluteGravity(drawerView, Gravity.LEFT)) { - mLeftDragger.smoothSlideViewTo(drawerView, 0, drawerView.getTop()); - } else { - mRightDragger.smoothSlideViewTo(drawerView, getWidth() - drawerView.getWidth(), - drawerView.getTop()); - } - } else { - moveDrawerToOffset(drawerView, 1.f); - updateDrawerState(lp.gravity, STATE_IDLE, drawerView); - drawerView.setVisibility(VISIBLE); - } - invalidate(); - } - - /** - * Open the specified drawer by animating it out of view. - * - * @param gravity Gravity.LEFT to move the left drawer or Gravity.RIGHT for the right. - * GravityCompat.START or GravityCompat.END may also be used. - */ - public void openDrawer(@EdgeGravity int gravity) { - openDrawer(gravity, true); - } - - /** - * Open the specified drawer. - * - * @param gravity Gravity.LEFT to move the left drawer or Gravity.RIGHT for the right. - * GravityCompat.START or GravityCompat.END may also be used. - * @param animate Whether opening of the drawer should be animated. - */ - public void openDrawer(@EdgeGravity int gravity, boolean animate) { - final View drawerView = findDrawerWithGravity(gravity); - if (drawerView == null) { - throw new IllegalArgumentException("No drawer view found with gravity " + - gravityToString(gravity)); - } - openDrawer(drawerView, animate); - } - - /** - * Close the specified drawer view by animating it into view. - * - * @param drawerView Drawer view to close - */ - public void closeDrawer(View drawerView) { - closeDrawer(drawerView, true); - } - - /** - * Close the specified drawer view. - * - * @param drawerView Drawer view to close - * @param animate Whether closing of the drawer should be animated. - */ - public void closeDrawer(View drawerView, boolean animate) { - if (!isDrawerView(drawerView)) { - throw new IllegalArgumentException("View " + drawerView + " is not a sliding drawer"); - } - - final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams(); - if (mFirstLayout) { - lp.onScreen = 0.f; - lp.openState = 0; - } else if (animate) { - lp.openState |= LayoutParams.FLAG_IS_CLOSING; - - if (checkDrawerViewAbsoluteGravity(drawerView, Gravity.LEFT)) { - mLeftDragger.smoothSlideViewTo(drawerView, -drawerView.getWidth(), - drawerView.getTop()); - } else { - mRightDragger.smoothSlideViewTo(drawerView, getWidth(), drawerView.getTop()); - } - } else { - moveDrawerToOffset(drawerView, 0.f); - updateDrawerState(lp.gravity, STATE_IDLE, drawerView); - drawerView.setVisibility(INVISIBLE); - } - invalidate(); - } - - /** - * Close the specified drawer by animating it out of view. - * - * @param gravity Gravity.LEFT to move the left drawer or Gravity.RIGHT for the right. - * GravityCompat.START or GravityCompat.END may also be used. - */ - public void closeDrawer(@EdgeGravity int gravity) { - closeDrawer(gravity, true); - } - - /** - * Close the specified drawer. - * - * @param gravity Gravity.LEFT to move the left drawer or Gravity.RIGHT for the right. - * GravityCompat.START or GravityCompat.END may also be used. - * @param animate Whether closing of the drawer should be animated. - */ - public void closeDrawer(@EdgeGravity int gravity, boolean animate) { - final View drawerView = findDrawerWithGravity(gravity); - if (drawerView == null) { - throw new IllegalArgumentException("No drawer view found with gravity " + - gravityToString(gravity)); - } - closeDrawer(drawerView, animate); - } - - /** - * Check if the given drawer view is currently in an open state. - * To be considered "open" the drawer must have settled into its fully - * visible state. To check for partial visibility use - * {@link #isDrawerVisible(android.view.View)}. - * - * @param drawer Drawer view to check - * @return true if the given drawer view is in an open state - * @see #isDrawerVisible(android.view.View) - */ - public boolean isDrawerOpen(View drawer) { - if (!isDrawerView(drawer)) { - throw new IllegalArgumentException("View " + drawer + " is not a drawer"); - } - LayoutParams drawerLp = (LayoutParams) drawer.getLayoutParams(); - return (drawerLp.openState & LayoutParams.FLAG_IS_OPENED) == 1; - } - - /** - * Check if the given drawer view is currently in an open state. - * To be considered "open" the drawer must have settled into its fully - * visible state. If there is no drawer with the given gravity this method - * will return false. - * - * @param drawerGravity Gravity of the drawer to check - * @return true if the given drawer view is in an open state - */ - public boolean isDrawerOpen(@EdgeGravity int drawerGravity) { - final View drawerView = findDrawerWithGravity(drawerGravity); - if (drawerView != null) { - return isDrawerOpen(drawerView); - } - return false; - } - - /** - * Check if a given drawer view is currently visible on-screen. The drawer - * may be only peeking onto the screen, fully extended, or anywhere inbetween. - * - * @param drawer Drawer view to check - * @return true if the given drawer is visible on-screen - * @see #isDrawerOpen(android.view.View) - */ - public boolean isDrawerVisible(View drawer) { - if (!isDrawerView(drawer)) { - throw new IllegalArgumentException("View " + drawer + " is not a drawer"); - } - return ((LayoutParams) drawer.getLayoutParams()).onScreen > 0; - } - - /** - * Check if a given drawer view is currently visible on-screen. The drawer - * may be only peeking onto the screen, fully extended, or anywhere in between. - * If there is no drawer with the given gravity this method will return false. - * - * @param drawerGravity Gravity of the drawer to check - * @return true if the given drawer is visible on-screen - */ - public boolean isDrawerVisible(@EdgeGravity int drawerGravity) { - final View drawerView = findDrawerWithGravity(drawerGravity); - if (drawerView != null) { - return isDrawerVisible(drawerView); - } - return false; - } - - private boolean hasPeekingDrawer() { - final int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - final LayoutParams lp = (LayoutParams) getChildAt(i).getLayoutParams(); - if (lp.isPeeking) { - return true; - } - } - return false; - } - - @Override - protected ViewGroup.LayoutParams generateDefaultLayoutParams() { - return new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT); - } - - @Override - protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) { - return p instanceof LayoutParams - ? new LayoutParams((LayoutParams) p) - : p instanceof ViewGroup.MarginLayoutParams - ? new LayoutParams((MarginLayoutParams) p) - : new LayoutParams(p); - } - - @Override - protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { - return p instanceof LayoutParams && super.checkLayoutParams(p); - } - - @Override - public ViewGroup.LayoutParams generateLayoutParams(AttributeSet attrs) { - return new LayoutParams(getContext(), attrs); - } - - @Override - public void addFocusables(ArrayList views, int direction, int focusableMode) { - if (getDescendantFocusability() == FOCUS_BLOCK_DESCENDANTS) { - return; - } - - // Only the views in the open drawers are focusables. Add normal child views when - // no drawers are opened. - final int childCount = getChildCount(); - boolean isDrawerOpen = false; - for (int i = 0; i < childCount; i++) { - final View child = getChildAt(i); - if (isDrawerView(child)) { - if (isDrawerOpen(child)) { - isDrawerOpen = true; - child.addFocusables(views, direction, focusableMode); - } - } else { - mNonDrawerViews.add(child); - } - } - - if (!isDrawerOpen) { - final int nonDrawerViewsCount = mNonDrawerViews.size(); - for (int i = 0; i < nonDrawerViewsCount; ++i) { - final View child = mNonDrawerViews.get(i); - if (child.getVisibility() == View.VISIBLE) { - child.addFocusables(views, direction, focusableMode); - } - } - } - - mNonDrawerViews.clear(); - } - - private boolean hasVisibleDrawer() { - return findVisibleDrawer() != null; - } - - private View findVisibleDrawer() { - final int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - final View child = getChildAt(i); - if (isDrawerView(child) && isDrawerVisible(child)) { - return child; - } - } - return null; - } - - void cancelChildViewTouch() { - // Cancel child touches - if (!mChildrenCanceledTouch) { - final long now = SystemClock.uptimeMillis(); - final MotionEvent cancelEvent = MotionEvent.obtain(now, now, - MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0); - final int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - getChildAt(i).dispatchTouchEvent(cancelEvent); - } - cancelEvent.recycle(); - mChildrenCanceledTouch = true; - } - } - - @Override - public boolean onKeyDown(int keyCode, KeyEvent event) { - if (keyCode == KeyEvent.KEYCODE_BACK && hasVisibleDrawer()) { - KeyEventCompat.startTracking(event); - return true; - } - return super.onKeyDown(keyCode, event); - } - - @Override - public boolean onKeyUp(int keyCode, KeyEvent event) { - if (keyCode == KeyEvent.KEYCODE_BACK) { - final View visibleDrawer = findVisibleDrawer(); - if (visibleDrawer != null && getDrawerLockMode(visibleDrawer) == LOCK_MODE_UNLOCKED) { - closeDrawers(); - } - return visibleDrawer != null; - } - return super.onKeyUp(keyCode, event); - } - - @Override - protected void onRestoreInstanceState(Parcelable state) { - if (!(state instanceof SavedState)) { - super.onRestoreInstanceState(state); - return; - } - - final SavedState ss = (SavedState) state; - super.onRestoreInstanceState(ss.getSuperState()); - - if (ss.openDrawerGravity != Gravity.NO_GRAVITY) { - final View toOpen = findDrawerWithGravity(ss.openDrawerGravity); - if (toOpen != null) { - openDrawer(toOpen); - } - } - - if (ss.lockModeLeft != LOCK_MODE_UNDEFINED) { - setDrawerLockMode(ss.lockModeLeft, Gravity.LEFT); - } - if (ss.lockModeRight != LOCK_MODE_UNDEFINED) { - setDrawerLockMode(ss.lockModeRight, Gravity.RIGHT); - } - if (ss.lockModeStart != LOCK_MODE_UNDEFINED) { - setDrawerLockMode(ss.lockModeStart, GravityCompat.START); - } - if (ss.lockModeEnd != LOCK_MODE_UNDEFINED) { - setDrawerLockMode(ss.lockModeEnd, GravityCompat.END); - } - } - - @Override - protected Parcelable onSaveInstanceState() { - final Parcelable superState = super.onSaveInstanceState(); - final SavedState ss = new SavedState(superState); - - final int childCount = getChildCount(); - for (int i = 0; i < childCount; i++) { - final View child = getChildAt(i); - LayoutParams lp = (LayoutParams) child.getLayoutParams(); - // Is the current child fully opened (that is, not closing)? - boolean isOpenedAndNotClosing = (lp.openState == LayoutParams.FLAG_IS_OPENED); - // Is the current child opening? - boolean isClosedAndOpening = (lp.openState == LayoutParams.FLAG_IS_OPENING); - if (isOpenedAndNotClosing || isClosedAndOpening) { - // If one of the conditions above holds, save the child's gravity - // so that we open that child during state restore. - ss.openDrawerGravity = lp.gravity; - break; - } - } - - ss.lockModeLeft = mLockModeLeft; - ss.lockModeRight = mLockModeRight; - ss.lockModeStart = mLockModeStart; - ss.lockModeEnd = mLockModeEnd; - - return ss; - } - - @Override - public void addView(View child, int index, ViewGroup.LayoutParams params) { - super.addView(child, index, params); - - final View openDrawer = findOpenDrawer(); - if (openDrawer != null || isDrawerView(child)) { - // A drawer is already open or the new view is a drawer, so the - // new view should start out hidden. - ViewCompat.setImportantForAccessibility(child, - ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS); - } else { - // Otherwise this is a content view and no drawer is open, so the - // new view should start out visible. - ViewCompat.setImportantForAccessibility(child, - ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES); - } - - // We only need a delegate here if the framework doesn't understand - // NO_HIDE_DESCENDANTS importance. - if (!CAN_HIDE_DESCENDANTS) { - ViewCompat.setAccessibilityDelegate(child, mChildAccessibilityDelegate); - } - } - - private static boolean includeChildForAccessibility(View child) { - // If the child is not important for accessibility we make - // sure this hides the entire subtree rooted at it as the - // IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDATS is not - // supported on older platforms but we want to hide the entire - // content and not opened drawers if a drawer is opened. - return ViewCompat.getImportantForAccessibility(child) - != ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS - && ViewCompat.getImportantForAccessibility(child) - != ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO; - } - - /** - * State persisted across instances - */ - protected static class SavedState extends AbsSavedState { - int openDrawerGravity = Gravity.NO_GRAVITY; - @LockMode int lockModeLeft; - @LockMode int lockModeRight; - @LockMode int lockModeStart; - @LockMode int lockModeEnd; - - public SavedState(Parcel in, ClassLoader loader) { - super(in, loader); - openDrawerGravity = in.readInt(); - lockModeLeft = in.readInt(); - lockModeRight = in.readInt(); - lockModeStart = in.readInt(); - lockModeEnd = in.readInt(); - } - - public SavedState(Parcelable superState) { - super(superState); - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - super.writeToParcel(dest, flags); - dest.writeInt(openDrawerGravity); - dest.writeInt(lockModeLeft); - dest.writeInt(lockModeRight); - dest.writeInt(lockModeStart); - dest.writeInt(lockModeEnd); - } - - public static final Creator CREATOR = ParcelableCompat.newCreator( - new ParcelableCompatCreatorCallbacks() { - @Override - public SavedState createFromParcel(Parcel in, ClassLoader loader) { - return new SavedState(in, loader); - } - - @Override - public SavedState[] newArray(int size) { - return new SavedState[size]; - } - }); - } - - private class ViewDragCallback extends ViewDragHelper.Callback { - private final int mAbsGravity; - private ViewDragHelper mDragger; - - private final Runnable mPeekRunnable = new Runnable() { - @Override public void run() { - peekDrawer(); - } - }; - - public ViewDragCallback(int gravity) { - mAbsGravity = gravity; - } - - public void setDragger(ViewDragHelper dragger) { - mDragger = dragger; - } - - public void removeCallbacks() { - DrawerLayout.this.removeCallbacks(mPeekRunnable); - } - - @Override - public boolean tryCaptureView(View child, int pointerId) { - // Only capture views where the gravity matches what we're looking for. - // This lets us use two ViewDragHelpers, one for each side drawer. - return isDrawerView(child) && checkDrawerViewAbsoluteGravity(child, mAbsGravity) - && getDrawerLockMode(child) == LOCK_MODE_UNLOCKED; - } - - @Override - public void onViewDragStateChanged(int state) { - updateDrawerState(mAbsGravity, state, mDragger.getCapturedView()); - } - - @Override - public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) { - float offset; - final int childWidth = changedView.getWidth(); - - // This reverses the positioning shown in onLayout. - if (checkDrawerViewAbsoluteGravity(changedView, Gravity.LEFT)) { - offset = (float) (childWidth + left) / childWidth; - } else { - final int width = getWidth(); - offset = (float) (width - left) / childWidth; - } - setDrawerViewOffset(changedView, offset); - changedView.setVisibility(offset == 0 ? INVISIBLE : VISIBLE); - invalidate(); - } - - @Override - public void onViewCaptured(View capturedChild, int activePointerId) { - final LayoutParams lp = (LayoutParams) capturedChild.getLayoutParams(); - lp.isPeeking = false; - - closeOtherDrawer(); - } - - private void closeOtherDrawer() { - final int otherGrav = mAbsGravity == Gravity.LEFT ? Gravity.RIGHT : Gravity.LEFT; - final View toClose = findDrawerWithGravity(otherGrav); - if (toClose != null) { - closeDrawer(toClose); - } - } - - @Override - public void onViewReleased(View releasedChild, float xvel, float yvel) { - // Offset is how open the drawer is, therefore left/right values - // are reversed from one another. - final float offset = getDrawerViewOffset(releasedChild); - final int childWidth = releasedChild.getWidth(); - - int left; - if (checkDrawerViewAbsoluteGravity(releasedChild, Gravity.LEFT)) { - left = xvel > 0 || xvel == 0 && offset > 0.5f ? 0 : -childWidth; - } else { - final int width = getWidth(); - left = xvel < 0 || xvel == 0 && offset > 0.5f ? width - childWidth : width; - } - - mDragger.settleCapturedViewAt(left, releasedChild.getTop()); - invalidate(); - } - - @Override - public void onEdgeTouched(int edgeFlags, int pointerId) { - postDelayed(mPeekRunnable, PEEK_DELAY); - } - - private void peekDrawer() { - final View toCapture; - final int childLeft; - final int peekDistance = mDragger.getEdgeSize(); - final boolean leftEdge = mAbsGravity == Gravity.LEFT; - if (leftEdge) { - toCapture = findDrawerWithGravity(Gravity.LEFT); - childLeft = (toCapture != null ? -toCapture.getWidth() : 0) + peekDistance; - } else { - toCapture = findDrawerWithGravity(Gravity.RIGHT); - childLeft = getWidth() - peekDistance; - } - // Only peek if it would mean making the drawer more visible and the drawer isn't locked - if (toCapture != null && ((leftEdge && toCapture.getLeft() < childLeft) || - (!leftEdge && toCapture.getLeft() > childLeft)) && - getDrawerLockMode(toCapture) == LOCK_MODE_UNLOCKED) { - final LayoutParams lp = (LayoutParams) toCapture.getLayoutParams(); - mDragger.smoothSlideViewTo(toCapture, childLeft, toCapture.getTop()); - lp.isPeeking = true; - invalidate(); - - closeOtherDrawer(); - - cancelChildViewTouch(); - } - } - - @Override - public boolean onEdgeLock(int edgeFlags) { - if (ALLOW_EDGE_LOCK) { - final View drawer = findDrawerWithGravity(mAbsGravity); - if (drawer != null && !isDrawerOpen(drawer)) { - closeDrawer(drawer); - } - return true; - } - return false; - } - - @Override - public void onEdgeDragStarted(int edgeFlags, int pointerId) { - final View toCapture; - if ((edgeFlags & ViewDragHelper.EDGE_LEFT) == ViewDragHelper.EDGE_LEFT) { - toCapture = findDrawerWithGravity(Gravity.LEFT); - } else { - toCapture = findDrawerWithGravity(Gravity.RIGHT); - } - - if (toCapture != null && getDrawerLockMode(toCapture) == LOCK_MODE_UNLOCKED) { - mDragger.captureChildView(toCapture, pointerId); - } - } - - @Override - public int getViewHorizontalDragRange(View child) { - return isDrawerView(child) ? child.getWidth() : 0; - } - - @Override - public int clampViewPositionHorizontal(View child, int left, int dx) { - if (checkDrawerViewAbsoluteGravity(child, Gravity.LEFT)) { - return Math.max(-child.getWidth(), Math.min(left, 0)); - } else { - final int width = getWidth(); - return Math.max(width - child.getWidth(), Math.min(left, width)); - } - } - - @Override - public int clampViewPositionVertical(View child, int top, int dy) { - return child.getTop(); - } - } - - public static class LayoutParams extends ViewGroup.MarginLayoutParams { - private static final int FLAG_IS_OPENED = 0x1; - private static final int FLAG_IS_OPENING = 0x2; - private static final int FLAG_IS_CLOSING = 0x4; - - public int gravity = Gravity.NO_GRAVITY; - private float onScreen; - private boolean isPeeking; - private int openState; - - public LayoutParams(Context c, AttributeSet attrs) { - super(c, attrs); - - final TypedArray a = c.obtainStyledAttributes(attrs, LAYOUT_ATTRS); - this.gravity = a.getInt(0, Gravity.NO_GRAVITY); - a.recycle(); - } - - public LayoutParams(int width, int height) { - super(width, height); - } - - public LayoutParams(int width, int height, int gravity) { - this(width, height); - this.gravity = gravity; - } - - public LayoutParams(LayoutParams source) { - super(source); - this.gravity = source.gravity; - } - - public LayoutParams(ViewGroup.LayoutParams source) { - super(source); - } - - public LayoutParams(ViewGroup.MarginLayoutParams source) { - super(source); - } - } - - class AccessibilityDelegate extends AccessibilityDelegateCompat { - private final Rect mTmpRect = new Rect(); - - @Override - public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) { - if (CAN_HIDE_DESCENDANTS) { - super.onInitializeAccessibilityNodeInfo(host, info); - } else { - // Obtain a node for the host, then manually generate the list - // of children to only include non-obscured views. - final AccessibilityNodeInfoCompat superNode = - AccessibilityNodeInfoCompat.obtain(info); - super.onInitializeAccessibilityNodeInfo(host, superNode); - - info.setSource(host); - final ViewParent parent = ViewCompat.getParentForAccessibility(host); - if (parent instanceof View) { - info.setParent((View) parent); - } - copyNodeInfoNoChildren(info, superNode); - superNode.recycle(); - - addChildrenForAccessibility(info, (ViewGroup) host); - } - - info.setClassName(DrawerLayout.class.getName()); - - // This view reports itself as focusable so that it can intercept - // the back button, but we should prevent this view from reporting - // itself as focusable to accessibility services. - info.setFocusable(false); - info.setFocused(false); - info.removeAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_FOCUS); - info.removeAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_CLEAR_FOCUS); - } - - @Override - public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) { - super.onInitializeAccessibilityEvent(host, event); - - event.setClassName(DrawerLayout.class.getName()); - } - - @Override - public boolean dispatchPopulateAccessibilityEvent(View host, AccessibilityEvent event) { - // Special case to handle window state change events. As far as - // accessibility services are concerned, state changes from - // DrawerLayout invalidate the entire contents of the screen (like - // an Activity or Dialog) and they should announce the title of the - // new content. - if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) { - final List eventText = event.getText(); - final View visibleDrawer = findVisibleDrawer(); - if (visibleDrawer != null) { - final int edgeGravity = getDrawerViewAbsoluteGravity(visibleDrawer); - final CharSequence title = getDrawerTitle(edgeGravity); - if (title != null) { - eventText.add(title); - } - } - - return true; - } - - return super.dispatchPopulateAccessibilityEvent(host, event); - } - - @Override - public boolean onRequestSendAccessibilityEvent(ViewGroup host, View child, - AccessibilityEvent event) { - if (CAN_HIDE_DESCENDANTS || includeChildForAccessibility(child)) { - return super.onRequestSendAccessibilityEvent(host, child, event); - } - return false; - } - - private void addChildrenForAccessibility(AccessibilityNodeInfoCompat info, ViewGroup v) { - final int childCount = v.getChildCount(); - for (int i = 0; i < childCount; i++) { - final View child = v.getChildAt(i); - if (includeChildForAccessibility(child)) { - info.addChild(child); - } - } - } - - /** - * This should really be in AccessibilityNodeInfoCompat, but there unfortunately - * seem to be a few elements that are not easily cloneable using the underlying API. - * Leave it private here as it's not general-purpose useful. - */ - private void copyNodeInfoNoChildren(AccessibilityNodeInfoCompat dest, - AccessibilityNodeInfoCompat src) { - final Rect rect = mTmpRect; - - src.getBoundsInParent(rect); - dest.setBoundsInParent(rect); - - src.getBoundsInScreen(rect); - dest.setBoundsInScreen(rect); - - dest.setVisibleToUser(src.isVisibleToUser()); - dest.setPackageName(src.getPackageName()); - dest.setClassName(src.getClassName()); - dest.setContentDescription(src.getContentDescription()); - - dest.setEnabled(src.isEnabled()); - dest.setClickable(src.isClickable()); - dest.setFocusable(src.isFocusable()); - dest.setFocused(src.isFocused()); - dest.setAccessibilityFocused(src.isAccessibilityFocused()); - dest.setSelected(src.isSelected()); - dest.setLongClickable(src.isLongClickable()); - - dest.addAction(src.getActions()); - } - } - - final class ChildAccessibilityDelegate extends AccessibilityDelegateCompat { - @Override - public void onInitializeAccessibilityNodeInfo(View child, - AccessibilityNodeInfoCompat info) { - super.onInitializeAccessibilityNodeInfo(child, info); - - if (!includeChildForAccessibility(child)) { - // If we are ignoring the sub-tree rooted at the child, - // break the connection to the rest of the node tree. - // For details refer to includeChildForAccessibility. - info.setParent(null); - } - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/loopeer/codereader/ui/view/DrawerLayoutCompatApi21.java b/app/src/main/java/com/loopeer/codereader/ui/view/DrawerLayoutCompatApi21.java deleted file mode 100644 index 109dd14..0000000 --- a/app/src/main/java/com/loopeer/codereader/ui/view/DrawerLayoutCompatApi21.java +++ /dev/null @@ -1,75 +0,0 @@ - -package com.loopeer.codereader.ui.view; - -import android.content.Context; -import android.content.res.TypedArray; -import android.graphics.drawable.Drawable; -import android.view.Gravity; -import android.view.View; -import android.view.ViewGroup; -import android.view.WindowInsets; - -class DrawerLayoutCompatApi21 { - - private static final int[] THEME_ATTRS = { - android.R.attr.colorPrimaryDark - }; - - public static void configureApplyInsets(View drawerLayout) { - if (drawerLayout instanceof DrawerLayoutImpl) { - drawerLayout.setOnApplyWindowInsetsListener(new InsetsListener()); - drawerLayout.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_STABLE - | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN); - } - } - - public static void dispatchChildInsets(View child, Object insets, int gravity) { - WindowInsets wi = (WindowInsets) insets; - if (gravity == Gravity.LEFT) { - wi = wi.replaceSystemWindowInsets(wi.getSystemWindowInsetLeft(), - wi.getSystemWindowInsetTop(), 0, wi.getSystemWindowInsetBottom()); - } else if (gravity == Gravity.RIGHT) { - wi = wi.replaceSystemWindowInsets(0, wi.getSystemWindowInsetTop(), - wi.getSystemWindowInsetRight(), wi.getSystemWindowInsetBottom()); - } - child.dispatchApplyWindowInsets(wi); - } - - public static void applyMarginInsets(ViewGroup.MarginLayoutParams lp, Object insets, - int gravity) { - WindowInsets wi = (WindowInsets) insets; - if (gravity == Gravity.LEFT) { - wi = wi.replaceSystemWindowInsets(wi.getSystemWindowInsetLeft(), - wi.getSystemWindowInsetTop(), 0, wi.getSystemWindowInsetBottom()); - } else if (gravity == Gravity.RIGHT) { - wi = wi.replaceSystemWindowInsets(0, wi.getSystemWindowInsetTop(), - wi.getSystemWindowInsetRight(), wi.getSystemWindowInsetBottom()); - } - lp.leftMargin = wi.getSystemWindowInsetLeft(); - lp.topMargin = wi.getSystemWindowInsetTop(); - lp.rightMargin = wi.getSystemWindowInsetRight(); - lp.bottomMargin = wi.getSystemWindowInsetBottom(); - } - - public static int getTopInset(Object insets) { - return insets != null ? ((WindowInsets) insets).getSystemWindowInsetTop() : 0; - } - - public static Drawable getDefaultStatusBarBackground(Context context) { - final TypedArray a = context.obtainStyledAttributes(THEME_ATTRS); - try { - return a.getDrawable(0); - } finally { - a.recycle(); - } - } - - static class InsetsListener implements View.OnApplyWindowInsetsListener { - @Override - public WindowInsets onApplyWindowInsets(View v, WindowInsets insets) { - final DrawerLayoutImpl drawerLayout = (DrawerLayoutImpl) v; - drawerLayout.setChildInsets(insets, insets.getSystemWindowInsetTop() > 0); - return insets.consumeSystemWindowInsets(); - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/loopeer/codereader/ui/view/DrawerLayoutImpl.java b/app/src/main/java/com/loopeer/codereader/ui/view/DrawerLayoutImpl.java deleted file mode 100644 index 6707d50..0000000 --- a/app/src/main/java/com/loopeer/codereader/ui/view/DrawerLayoutImpl.java +++ /dev/null @@ -1,5 +0,0 @@ -package com.loopeer.codereader.ui.view; - -interface DrawerLayoutImpl { - void setChildInsets(Object insets, boolean drawStatusBar); -} \ No newline at end of file diff --git a/app/src/main/java/com/loopeer/codereader/ui/view/ForegroundProgressRelativeLayout.java b/app/src/main/java/com/loopeer/codereader/ui/view/ForegroundProgressRelativeLayout.java deleted file mode 100644 index a84e4ed..0000000 --- a/app/src/main/java/com/loopeer/codereader/ui/view/ForegroundProgressRelativeLayout.java +++ /dev/null @@ -1,125 +0,0 @@ -package com.loopeer.codereader.ui.view; - -import android.animation.ValueAnimator; -import android.content.Context; -import android.content.res.TypedArray; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.Rect; -import android.support.v4.content.ContextCompat; -import android.util.AttributeSet; - -import com.loopeer.codereader.R; - -public class ForegroundProgressRelativeLayout extends ForegroundRelativeLayout { - - private Paint mRemainderPaint; - private Paint mProgressPaint; - - private float mProgressCurrent; - private float mProgressPre; - private float mProgressShow; - private boolean mIsUnzip; - private int mRemainderColor; - private static int sProgressTextPadding; - private static int sUnzipTextPadding; - - public ForegroundProgressRelativeLayout(Context context) { - super(context); - } - - public ForegroundProgressRelativeLayout(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public ForegroundProgressRelativeLayout(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - - TypedArray a = getContext().obtainStyledAttributes(attrs, R.styleable.ForegroundProgressRelativeLayout, - defStyle, 0); - mRemainderColor = a.getColor(R.styleable.ForegroundProgressRelativeLayout_remainderColor - , ContextCompat.getColor(getContext(), R.color.repo_download_remainder_color)); - init(); - setWillNotDraw(false); - } - - private void init() { - sProgressTextPadding = getResources().getDimensionPixelSize(R.dimen.inline_padding); - sUnzipTextPadding = getResources().getDimensionPixelSize(R.dimen.medium_padding); - - mRemainderPaint = new Paint(); - mRemainderPaint.setColor(mRemainderColor); - mRemainderPaint.setStyle(Paint.Style.FILL); - - mProgressPaint = new Paint(); - mProgressPaint.setAntiAlias(true); - mProgressPaint.setColor(ContextCompat.getColor(getContext(), R.color.colorPrimary)); - mProgressPaint.setStyle(Paint.Style.FILL); - mProgressPaint.setTextSize(getResources().getDimension(R.dimen.text_size_xxsmall)); - } - - public void setProgressCurrent(float i) { - if (mProgressCurrent != i && i != 0f) { - mProgressPre = mProgressCurrent; - mProgressCurrent = i; - postProgressAnimation(); - } else if (i == 0f){ - mProgressCurrent = i; - mProgressShow = 0; - invalidate(); - } - } - - public void setInitProgress(float i) { - mProgressPre = i; - mProgressCurrent = i; - mProgressShow = i; - invalidate(); - } - - private void postProgressAnimation() { - ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 1f); - valueAnimator.addUpdateListener(valueAnimator1 -> { - float fraction = valueAnimator1.getAnimatedFraction(); - mProgressShow = mProgressPre + fraction * (mProgressCurrent - mProgressPre); - invalidate(); - }); - valueAnimator.setDuration(500); - valueAnimator.start(); - } - - public void setUnzip(boolean b) { - mIsUnzip = b; - if (mProgressCurrent == 1.f) { - invalidate(); - } - } - - @Override - protected void onDraw(Canvas canvas) { - super.onDraw(canvas); - - if (mProgressCurrent <= 1f) { - canvas.drawRect(getWidth() * mProgressShow, 0, getWidth(), getHeight(), mRemainderPaint); - String content = String.format("%.0f", mProgressShow * 100) + "%"; - Rect bounds = new Rect(); - mProgressPaint.getTextBounds(content, 0, content.length(), bounds); - if (getWidth() * (1 - mProgressShow) > bounds.width()) { - canvas.drawText(content - , getWidth() * mProgressShow + sProgressTextPadding - , getHeight() - sProgressTextPadding - , mProgressPaint); - } - } - - if (mProgressCurrent == 1f && mIsUnzip) { - String content = getResources().getString(R.string.repo_download_isunzip); - Rect bounds = new Rect(); - mProgressPaint.getTextBounds(content, 0, content.length(), bounds); - canvas.drawText(content - , getWidth() - sUnzipTextPadding - bounds.width() - , getHeight() - sProgressTextPadding - , mProgressPaint); - } - } -} diff --git a/app/src/main/java/com/loopeer/codereader/ui/view/ForegroundRelativeLayout.java b/app/src/main/java/com/loopeer/codereader/ui/view/ForegroundRelativeLayout.java deleted file mode 100644 index c85468e..0000000 --- a/app/src/main/java/com/loopeer/codereader/ui/view/ForegroundRelativeLayout.java +++ /dev/null @@ -1,228 +0,0 @@ -/* - * Copyright (C) 2006 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.loopeer.codereader.ui.view; - -import android.annotation.TargetApi; -import android.content.Context; -import android.content.res.TypedArray; -import android.graphics.Canvas; -import android.graphics.Rect; -import android.graphics.drawable.Drawable; -import android.os.Build; -import android.util.AttributeSet; -import android.view.Gravity; -import android.widget.RelativeLayout; - -import com.loopeer.codereader.R; - -public class ForegroundRelativeLayout extends RelativeLayout { - - private Drawable mForeground; - - private final Rect mSelfBounds = new Rect(); - private final Rect mOverlayBounds = new Rect(); - - private int mForegroundGravity = Gravity.FILL; - - protected boolean mForegroundInPadding = true; - - boolean mForegroundBoundsChanged = false; - - public ForegroundRelativeLayout(Context context) { - super(context); - } - - public ForegroundRelativeLayout(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public ForegroundRelativeLayout(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - - TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ForegroundRelativeLayout, - defStyle, 0); - - mForegroundGravity = a.getInt( - R.styleable.ForegroundRelativeLayout_android_foregroundGravity, mForegroundGravity); - - final Drawable d = a.getDrawable(R.styleable.ForegroundRelativeLayout_android_foreground); - if (d != null) { - setForeground(d); - } - - mForegroundInPadding = a.getBoolean( - R.styleable.ForegroundRelativeLayout_android_foregroundInsidePadding, true); - - a.recycle(); - } - - /** - * Describes how the foreground is positioned. - * - * @return foreground gravity. - * @see #setForegroundGravity(int) - */ - public int getForegroundGravity() { - return mForegroundGravity; - } - - /** - * Describes how the foreground is positioned. Defaults to START and TOP. - * - * @param foregroundGravity See {@link Gravity} - * @see #getForegroundGravity() - */ - public void setForegroundGravity(int foregroundGravity) { - if (mForegroundGravity != foregroundGravity) { - if ((foregroundGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK) == 0) { - foregroundGravity |= Gravity.START; - } - - if ((foregroundGravity & Gravity.VERTICAL_GRAVITY_MASK) == 0) { - foregroundGravity |= Gravity.TOP; - } - - mForegroundGravity = foregroundGravity; - - - if (mForegroundGravity == Gravity.FILL && mForeground != null) { - Rect padding = new Rect(); - mForeground.getPadding(padding); - } - - requestLayout(); - } - } - - @Override - protected boolean verifyDrawable(Drawable who) { - return super.verifyDrawable(who) || (who == mForeground); - } - - @Override - public void jumpDrawablesToCurrentState() { - super.jumpDrawablesToCurrentState(); - if (mForeground != null) mForeground.jumpToCurrentState(); - } - - @Override - protected void drawableStateChanged() { - super.drawableStateChanged(); - if (mForeground != null && mForeground.isStateful()) { - mForeground.setState(getDrawableState()); - } - } - - /** - * Supply a Drawable that is to be rendered on top of all of the child - * views in the frame layout. Any padding in the Drawable will be taken - * into account by ensuring that the children are inset to be placed - * inside of the padding area. - * - * @param drawable The Drawable to be drawn on top of the children. - */ - public void setForeground(Drawable drawable) { - if (mForeground != drawable) { - if (mForeground != null) { - mForeground.setCallback(null); - unscheduleDrawable(mForeground); - } - - mForeground = drawable; - - if (drawable != null) { - setWillNotDraw(false); - drawable.setCallback(this); - if (drawable.isStateful()) { - drawable.setState(getDrawableState()); - } - if (mForegroundGravity == Gravity.FILL) { - Rect padding = new Rect(); - drawable.getPadding(padding); - } - } else { - setWillNotDraw(true); - } - requestLayout(); - invalidate(); - } - } - - /** - * Returns the drawable used as the foreground of this FrameLayout. The - * foreground drawable, if non-null, is always drawn on top of the children. - * - * @return A Drawable or null if no foreground was set. - */ - public Drawable getForeground() { - return mForeground; - } - - @Override - protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - super.onLayout(changed, left, top, right, bottom); - if (changed) - mForegroundBoundsChanged = changed; - } - - @Override - protected void onSizeChanged(int w, int h, int oldw, int oldh) { - super.onSizeChanged(w, h, oldw, oldh); - mForegroundBoundsChanged = true; - } - - @Override - public void draw(Canvas canvas) { - super.draw(canvas); - - if (mForeground != null) { - final Drawable foreground = mForeground; - - if (mForegroundBoundsChanged) { - mForegroundBoundsChanged = false; - final Rect selfBounds = mSelfBounds; - final Rect overlayBounds = mOverlayBounds; - - final int w = getRight() - getLeft(); - final int h = getBottom() - getTop(); - - if (mForegroundInPadding) { - selfBounds.set(0, 0, w, h); - } else { - selfBounds.set(getPaddingLeft(), getPaddingTop(), - w - getPaddingRight(), h - getPaddingBottom()); - } - - Gravity.apply(mForegroundGravity, foreground.getIntrinsicWidth(), - foreground.getIntrinsicHeight(), selfBounds, overlayBounds); - foreground.setBounds(overlayBounds); - } - - foreground.draw(canvas); - } - } - - @TargetApi(Build.VERSION_CODES.LOLLIPOP) - @Override - public void drawableHotspotChanged(float x, float y) { - super.drawableHotspotChanged(x, y); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - if (mForeground != null) { - mForeground.setHotspot(x, y); - } - } - } -} diff --git a/app/src/main/java/com/loopeer/codereader/ui/view/ForegroundTextView.java b/app/src/main/java/com/loopeer/codereader/ui/view/ForegroundTextView.java deleted file mode 100644 index 52b145e..0000000 --- a/app/src/main/java/com/loopeer/codereader/ui/view/ForegroundTextView.java +++ /dev/null @@ -1,110 +0,0 @@ -/** - * Created by YuGang Yang on April 08, 2015. - * Copyright 2007-2015 Laputapp.com. All rights reserved. - */ -package com.loopeer.codereader.ui.view; - -import android.content.Context; -import android.content.res.TypedArray; -import android.graphics.Canvas; -import android.graphics.drawable.Drawable; -import android.util.AttributeSet; -import android.widget.TextView; - -import com.loopeer.codereader.R; - -public class ForegroundTextView extends TextView { - private Drawable foreground; - - public ForegroundTextView(Context context) { - this(context, null); - } - - public ForegroundTextView(Context context, AttributeSet attrs) { - super(context, attrs); - - TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ForegroundTextView); - Drawable foreground = a.getDrawable(R.styleable.ForegroundTextView_android_foreground); - if (foreground != null) { - setForeground(foreground); - } - a.recycle(); - } - - /** - * Supply a drawable resource that is to be rendered on top of all of the child - * views in the frame layout. - * - * @param drawableResId The drawable resource to be drawn on top of the children. - */ - public void setForegroundResource(int drawableResId) { - setForeground(getContext().getResources().getDrawable(drawableResId)); - } - - /** - * Supply a Drawable that is to be rendered on top of all of the child - * views in the frame layout. - * - * @param drawable The Drawable to be drawn on top of the children. - */ - public void setForeground(Drawable drawable) { - if (foreground == drawable) { - return; - } - if (foreground != null) { - foreground.setCallback(null); - unscheduleDrawable(foreground); - } - - foreground = drawable; - - if (drawable != null) { - drawable.setCallback(this); - if (drawable.isStateful()) { - drawable.setState(getDrawableState()); - } - } - requestLayout(); - invalidate(); - } - - @Override protected boolean verifyDrawable(Drawable who) { - return super.verifyDrawable(who) || who == foreground; - } - - @Override public void jumpDrawablesToCurrentState() { - super.jumpDrawablesToCurrentState(); - if (foreground != null) foreground.jumpToCurrentState(); - } - - @Override protected void drawableStateChanged() { - super.drawableStateChanged(); - if (foreground != null && foreground.isStateful()) { - foreground.setState(getDrawableState()); - } - } - - @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - if (foreground != null) { - foreground.setBounds(0, 0, getMeasuredWidth(), getMeasuredHeight()); - invalidate(); - } - } - - @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { - super.onSizeChanged(w, h, oldw, oldh); - if (foreground != null) { - foreground.setBounds(0, 0, w, h); - invalidate(); - } - } - - @Override public void draw(Canvas canvas) { - super.draw(canvas); - - if (foreground != null) { - foreground.draw(canvas); - } - } -} diff --git a/app/src/main/java/com/loopeer/codereader/ui/view/NestHorizontalScrollView.java b/app/src/main/java/com/loopeer/codereader/ui/view/NestHorizontalScrollView.java deleted file mode 100644 index 3811336..0000000 --- a/app/src/main/java/com/loopeer/codereader/ui/view/NestHorizontalScrollView.java +++ /dev/null @@ -1,1844 +0,0 @@ -package com.loopeer.codereader.ui.view; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.content.res.TypedArray; -import android.graphics.Canvas; -import android.graphics.Rect; -import android.os.Bundle; -import android.os.Parcel; -import android.os.Parcelable; -import android.support.v4.view.AccessibilityDelegateCompat; -import android.support.v4.view.InputDeviceCompat; -import android.support.v4.view.MotionEventCompat; -import android.support.v4.view.NestedScrollingChild; -import android.support.v4.view.NestedScrollingChildHelper; -import android.support.v4.view.NestedScrollingParent; -import android.support.v4.view.NestedScrollingParentHelper; -import android.support.v4.view.VelocityTrackerCompat; -import android.support.v4.view.ViewCompat; -import android.support.v4.view.accessibility.AccessibilityEventCompat; -import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat; -import android.support.v4.view.accessibility.AccessibilityRecordCompat; -import android.support.v4.widget.EdgeEffectCompat; -import android.support.v4.widget.ScrollerCompat; -import android.util.AttributeSet; -import android.util.Log; -import android.util.TypedValue; -import android.view.FocusFinder; -import android.view.KeyEvent; -import android.view.MotionEvent; -import android.view.VelocityTracker; -import android.view.View; -import android.view.ViewConfiguration; -import android.view.ViewGroup; -import android.view.ViewParent; -import android.view.accessibility.AccessibilityEvent; -import android.view.animation.AnimationUtils; -import android.widget.FrameLayout; -import android.widget.ScrollView; - -import java.util.List; - -public class NestHorizontalScrollView extends FrameLayout implements NestedScrollingParent, - NestedScrollingChild { - static final int ANIMATED_SCROLL_GAP = 250; - - static final float MAX_SCROLL_FACTOR = 0.5f; - - private static final String TAG = "NestedScrollView"; - - private long mLastScroll; - - private final Rect mTempRect = new Rect(); - private ScrollerCompat mScroller; - private EdgeEffectCompat mEdgeGlowLeft; - private EdgeEffectCompat mEdgeGlowRight; - - /** - * Position of the last motion event. - */ - private int mLastMotionX; - private int mLastMotionY; - - /** - * True when the layout has changed but the traversal has not come through yet. - * Ideally the view hierarchy would keep track of this for us. - */ - private boolean mIsLayoutDirty = true; - private boolean mIsLaidOut = false; - - /** - * The child to give focus to in the event that a child has requested focus while the - * layout is dirty. This prevents the scroll from being wrong if the child has not been - * laid out before requesting focus. - */ - private View mChildToScrollTo = null; - - /** - * True if the user is currently dragging this ScrollView around. This is - * not the same as 'is being flinged', which can be checked by - * mScroller.isFinished() (flinging begins when the user lifts his finger). - */ - private boolean mIsBeingDragged = false; - - /** - * Determines speed during touch scrolling - */ - private VelocityTracker mVelocityTracker; - - /** - * When set to true, the scroll view measure its child to make it fill the currently - * visible area. - */ - private boolean mFillViewport; - - /** - * Whether arrow scrolling is animated. - */ - private boolean mSmoothScrollingEnabled = true; - - private int mTouchSlop; - private int mMinimumVelocity; - private int mMaximumVelocity; - - /** - * ID of the active pointer. This is used to retain consistency during - * drags/flings if multiple pointers are used. - */ - private int mActivePointerId = INVALID_POINTER; - - /** - * Used during scrolling to retrieve the new offset within the window. - */ - private final int[] mScrollOffset = new int[2]; - private final int[] mScrollConsumed = new int[2]; - private int mNestedXOffset; - - /** - * Sentinel value for no current active pointer. - * Used by {@link #mActivePointerId}. - */ - private static final int INVALID_POINTER = -1; - - private SavedState mSavedState; - - private static final AccessibilityDelegate ACCESSIBILITY_DELEGATE = new AccessibilityDelegate(); - - private static final int[] SCROLLVIEW_STYLEABLE = new int[] { - android.R.attr.fillViewport - }; - - private final NestedScrollingParentHelper mParentHelper; - private final NestedScrollingChildHelper mChildHelper; - - private float mHorizontalScrollFactor; - - public NestHorizontalScrollView(Context context) { - this(context, null); - } - - public NestHorizontalScrollView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public NestHorizontalScrollView(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - initScrollView(); - - final TypedArray a = context.obtainStyledAttributes( - attrs, SCROLLVIEW_STYLEABLE, defStyleAttr, 0); - - setFillViewport(a.getBoolean(0, false)); - - a.recycle(); - - mParentHelper = new NestedScrollingParentHelper(this); - mChildHelper = new NestedScrollingChildHelper(this); - - // ...because why else would you be using this widget? - setNestedScrollingEnabled(true); - - ViewCompat.setAccessibilityDelegate(this, ACCESSIBILITY_DELEGATE); - } - - // NestedScrollingChild - - @Override - public void setNestedScrollingEnabled(boolean enabled) { - mChildHelper.setNestedScrollingEnabled(enabled); - } - - @Override - public boolean isNestedScrollingEnabled() { - return mChildHelper.isNestedScrollingEnabled(); - } - - @Override - public boolean startNestedScroll(int axes) { - return mChildHelper.startNestedScroll(axes); - } - - @Override - public void stopNestedScroll() { - mChildHelper.stopNestedScroll(); - } - - @Override - public boolean hasNestedScrollingParent() { - return mChildHelper.hasNestedScrollingParent(); - } - - @Override - public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, - int dyUnconsumed, int[] offsetInWindow) { - return mChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, - offsetInWindow); - } - - @Override - public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) { - return mChildHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow); - } - - @Override - public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) { - return mChildHelper.dispatchNestedFling(velocityX, velocityY, consumed); - } - - @Override - public boolean dispatchNestedPreFling(float velocityX, float velocityY) { - return mChildHelper.dispatchNestedPreFling(velocityX, velocityY); - } - - // NestedScrollingParent - - @Override - public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) { - return (nestedScrollAxes & ViewCompat.SCROLL_AXIS_HORIZONTAL) != 0; - } - - @Override - public void onNestedScrollAccepted(View child, View target, int nestedScrollAxes) { - mParentHelper.onNestedScrollAccepted(child, target, nestedScrollAxes); - startNestedScroll(ViewCompat.SCROLL_AXIS_HORIZONTAL); - } - - @Override - public void onStopNestedScroll(View target) { - stopNestedScroll(); - } - - @Override - public void onNestedScroll(View target, int dxConsumed, int dyConsumed, int dxUnconsumed, - int dyUnconsumed) { - final int oldScrollX = getScrollX(); - scrollBy(dxUnconsumed, 0); - final int mxConsumed = getScrollX() - oldScrollX; - final int mxUnconsumed = dxUnconsumed - mxConsumed; - dispatchNestedScroll(mxConsumed, 0, mxUnconsumed, 0, null); - } - - @Override - public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) { - // Do nothing - } - - @Override - public boolean onNestedFling(View target, float velocityX, float velocityY, boolean consumed) { - if (!consumed) { - flingWithNestedDispatch((int) velocityX); - return true; - } - return false; - } - - @Override - public boolean onNestedPreFling(View target, float velocityX, float velocityY) { - // Do nothing - return false; - } - - @Override - public int getNestedScrollAxes() { - return mParentHelper.getNestedScrollAxes(); - } - - // ScrollView import - - public boolean shouldDelayChildPressedState() { - return true; - } - - @Override - protected float getLeftFadingEdgeStrength() { - if (getChildCount() == 0) { - return 0.0f; - } - - final int length = getHorizontalFadingEdgeLength(); - final int scrollX = getScrollX(); - if (scrollX < length) { - return scrollX / (float) length; - } - - return 1.0f; - } - - @Override - protected float getRightFadingEdgeStrength() { - if (getChildCount() == 0) { - return 0.0f; - } - - final int length = getHorizontalFadingEdgeLength(); - final int rightEdge = getWidth() - getPaddingRight(); - final int span = getChildAt(0).getRight() - getScrollX() - rightEdge; - if (span < length) { - return span / (float) length; - } - - return 1.0f; - } - - /** - * @return The maximum amount this scroll view will scroll in response to - * an arrow event. - */ - public int getMaxScrollAmount() { - return (int) (MAX_SCROLL_FACTOR * getWidth()); - } - - private void initScrollView() { - mScroller = ScrollerCompat.create(getContext(), null); - setFocusable(true); - setDescendantFocusability(FOCUS_AFTER_DESCENDANTS); - setWillNotDraw(false); - final ViewConfiguration configuration = ViewConfiguration.get(getContext()); - mTouchSlop = configuration.getScaledTouchSlop(); - mMinimumVelocity = configuration.getScaledMinimumFlingVelocity(); - mMaximumVelocity = configuration.getScaledMaximumFlingVelocity(); - } - - @Override - public void addView(View child) { - if (getChildCount() > 0) { - throw new IllegalStateException("ScrollView can host only one direct child"); - } - - super.addView(child); - } - - @Override - public void addView(View child, int index) { - if (getChildCount() > 0) { - throw new IllegalStateException("ScrollView can host only one direct child"); - } - - super.addView(child, index); - } - - @Override - public void addView(View child, ViewGroup.LayoutParams params) { - if (getChildCount() > 0) { - throw new IllegalStateException("ScrollView can host only one direct child"); - } - - super.addView(child, params); - } - - @Override - public void addView(View child, int index, ViewGroup.LayoutParams params) { - if (getChildCount() > 0) { - throw new IllegalStateException("ScrollView can host only one direct child"); - } - - super.addView(child, index, params); - } - - /** - * @return Returns true this ScrollView can be scrolled - */ - private boolean canScroll() { - View child = getChildAt(0); - if (child != null) { - int childWidth = child.getWidth(); - return getWidth() < childWidth + getPaddingRight() + getPaddingLeft(); - } - return false; - } - - /** - * Indicates whether this ScrollView's content is stretched to fill the viewport. - * - * @return True if the content fills the viewport, false otherwise. - * - * @attr ref android.R.styleable#ScrollView_fillViewport - */ - public boolean isFillViewport() { - return mFillViewport; - } - - /** - * Indicates this ScrollView whether it should stretch its content height to fill - * the viewport or not. - * - * @param fillViewport True to stretch the content's height to the viewport's - * boundaries, false otherwise. - * - * @attr ref android.R.styleable#ScrollView_fillViewport - */ - public void setFillViewport(boolean fillViewport) { - if (fillViewport != mFillViewport) { - mFillViewport = fillViewport; - requestLayout(); - } - } - - /** - * @return Whether arrow scrolling will animate its transition. - */ - public boolean isSmoothScrollingEnabled() { - return mSmoothScrollingEnabled; - } - - /** - * Set whether arrow scrolling will animate its transition. - * @param smoothScrollingEnabled whether arrow scrolling will animate its transition - */ - public void setSmoothScrollingEnabled(boolean smoothScrollingEnabled) { - mSmoothScrollingEnabled = smoothScrollingEnabled; - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - - if (!mFillViewport) { - return; - } - - final int heightMode = MeasureSpec.getMode(heightMeasureSpec); - if (heightMode == MeasureSpec.UNSPECIFIED) { - return; - } - - if (getChildCount() > 0) { - final View child = getChildAt(0); - int width = getMeasuredWidth(); - if (child.getMeasuredWidth() < width) { - final FrameLayout.LayoutParams lp = (LayoutParams) child.getLayoutParams(); - - int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, - getPaddingTop() + getPaddingBottom(), lp.height); - width -= getPaddingRight(); - width -= getPaddingLeft(); - int childWidthMeasureSpec = - MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY); - - child.measure(childWidthMeasureSpec, childHeightMeasureSpec); - } - } - } - - @Override - public boolean dispatchKeyEvent(KeyEvent event) { - // Let the focused view and/or our descendants get the key first - return super.dispatchKeyEvent(event) || executeKeyEvent(event); - } - - /** - * You can call this function yourself to have the scroll view perform - * scrolling from a key event, just as if the event had been dispatched to - * it by the view hierarchy. - * - * @param event The key event to execute. - * @return Return true if the event was handled, else false. - */ - public boolean executeKeyEvent(KeyEvent event) { - mTempRect.setEmpty(); - - if (!canScroll()) { - if (isFocused() && event.getKeyCode() != KeyEvent.KEYCODE_BACK) { - View currentFocused = findFocus(); - if (currentFocused == this) currentFocused = null; - View nextFocused = FocusFinder.getInstance().findNextFocus(this, - currentFocused, View.FOCUS_RIGHT); - return nextFocused != null - && nextFocused != this - && nextFocused.requestFocus(View.FOCUS_RIGHT); - } - return false; - } - - boolean handled = false; - if (event.getAction() == KeyEvent.ACTION_DOWN) { - switch (event.getKeyCode()) { - case KeyEvent.KEYCODE_DPAD_LEFT: - if (!event.isAltPressed()) { - handled = arrowScroll(View.FOCUS_LEFT); - } else { - handled = fullScroll(View.FOCUS_LEFT); - } - break; - case KeyEvent.KEYCODE_DPAD_RIGHT: - if (!event.isAltPressed()) { - handled = arrowScroll(View.FOCUS_RIGHT); - } else { - handled = fullScroll(View.FOCUS_RIGHT); - } - break; - case KeyEvent.KEYCODE_SPACE: - pageScroll(event.isShiftPressed() ? View.FOCUS_LEFT : View.FOCUS_RIGHT); - break; - } - } - - return handled; - } - - private boolean inChild(int x, int y) { - if (getChildCount() > 0) { - final int scrollX = getScrollX(); - final View child = getChildAt(0); - return !(x < child.getLeft() - scrollX - || x >= child.getRight() - scrollX - || y < child.getTop() - || y >= child.getBottom()); - } - return false; - } - - private void initOrResetVelocityTracker() { - if (mVelocityTracker == null) { - mVelocityTracker = VelocityTracker.obtain(); - } else { - mVelocityTracker.clear(); - } - } - - private void initVelocityTrackerIfNotExists() { - if (mVelocityTracker == null) { - mVelocityTracker = VelocityTracker.obtain(); - } - } - - private void recycleVelocityTracker() { - if (mVelocityTracker != null) { - mVelocityTracker.recycle(); - mVelocityTracker = null; - } - } - - @Override - public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { - if (disallowIntercept) { - recycleVelocityTracker(); - } - super.requestDisallowInterceptTouchEvent(disallowIntercept); - } - - - @Override - public boolean onInterceptTouchEvent(MotionEvent ev) { - /* - * This method JUST determines whether we want to intercept the motion. - * If we return true, onMotionEvent will be called and we do the actual - * scrolling there. - */ - - /* - * Shortcut the most recurring case: the user is in the dragging - * state and he is moving his finger. We want to intercept this - * motion. - */ - final int action = ev.getAction(); - if ((action == MotionEvent.ACTION_MOVE) && (mIsBeingDragged)) { - return true; - } - - switch (action & MotionEventCompat.ACTION_MASK) { - case MotionEvent.ACTION_MOVE: { - /* - * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check - * whether the user has moved far enough from his original down touch. - */ - - /* - * Locally do absolute value. mLastMotionY is set to the y value - * of the down event. - */ - final int activePointerId = mActivePointerId; - if (activePointerId == INVALID_POINTER) { - // If we don't have a valid id, the touch down wasn't on content. - break; - } - - final int pointerIndex = MotionEventCompat.findPointerIndex(ev, activePointerId); - if (pointerIndex == -1) { - Log.e(TAG, "Invalid pointerId=" + activePointerId - + " in onInterceptTouchEvent"); - break; - } - - final int x = (int) MotionEventCompat.getX(ev, pointerIndex); - final int y = (int) MotionEventCompat.getY(ev, pointerIndex); - final int xDiff = Math.abs(x - mLastMotionX); - final int yDiff = Math.abs(y - mLastMotionY); - if (xDiff > mTouchSlop && xDiff > yDiff - && (getNestedScrollAxes() & ViewCompat.SCROLL_AXIS_HORIZONTAL) == 0) { - mIsBeingDragged = true; - mLastMotionX = x; - mLastMotionY = y; - initVelocityTrackerIfNotExists(); - mVelocityTracker.addMovement(ev); - mNestedXOffset = 0; - final ViewParent parent = getParent(); - if (parent != null) { - parent.requestDisallowInterceptTouchEvent(true); - } - } else { - mIsBeingDragged = false; - } - break; - } - - case MotionEvent.ACTION_DOWN: { - final int x = (int) ev.getX(); - final int y = (int) ev.getY(); - if (!inChild(x, y)) { - mIsBeingDragged = false; - recycleVelocityTracker(); - break; - } - - /* - * Remember location of down touch. - * ACTION_DOWN always refers to pointer index 0. - */ - mLastMotionX = x; - mLastMotionY = y; - mActivePointerId = MotionEventCompat.getPointerId(ev, 0); - - initOrResetVelocityTracker(); - mVelocityTracker.addMovement(ev); - /* - * If being flinged and user touches the screen, initiate drag; - * otherwise don't. mScroller.isFinished should be false when - * being flinged. - */ - mIsBeingDragged = !mScroller.isFinished(); - startNestedScroll(ViewCompat.SCROLL_AXIS_HORIZONTAL); - break; - } - - case MotionEvent.ACTION_CANCEL: - case MotionEvent.ACTION_UP: - /* Release the drag */ - mIsBeingDragged = false; - mActivePointerId = INVALID_POINTER; - recycleVelocityTracker(); - stopNestedScroll(); - break; - case MotionEventCompat.ACTION_POINTER_UP: - onSecondaryPointerUp(ev); - break; - } - - /* - * The only time we want to intercept motion events is if we are in the - * drag mode. - */ - return mIsBeingDragged; - } - - @Override - public boolean onTouchEvent(MotionEvent ev) { - initVelocityTrackerIfNotExists(); - - MotionEvent vtev = MotionEvent.obtain(ev); - - final int actionMasked = MotionEventCompat.getActionMasked(ev); - - if (actionMasked == MotionEvent.ACTION_DOWN) { - mNestedXOffset = 0; - } - vtev.offsetLocation(mNestedXOffset, 0); - - switch (actionMasked) { - case MotionEvent.ACTION_DOWN: { - if (getChildCount() == 0) { - return false; - } - if ((mIsBeingDragged = !mScroller.isFinished())) { - final ViewParent parent = getParent(); - if (parent != null) { - parent.requestDisallowInterceptTouchEvent(true); - } - } - - /* - * If being flinged and user touches, stop the fling. isFinished - * will be false if being flinged. - */ - if (!mScroller.isFinished()) { - mScroller.abortAnimation(); - } - - // Remember where the motion event started - mLastMotionX = (int) ev.getX(); - mActivePointerId = MotionEventCompat.getPointerId(ev, 0); - startNestedScroll(ViewCompat.SCROLL_AXIS_HORIZONTAL); - break; - } - case MotionEvent.ACTION_MOVE: - final int activePointerIndex = MotionEventCompat.findPointerIndex(ev, - mActivePointerId); - if (activePointerIndex == -1) { - Log.e(TAG, "Invalid pointerId=" + mActivePointerId + " in onTouchEvent"); - break; - } - - final int x = (int) MotionEventCompat.getX(ev, activePointerIndex); - int deltaX = mLastMotionX - x; - if (dispatchNestedPreScroll(deltaX, 0, mScrollConsumed, mScrollOffset)) { - deltaX -= mScrollConsumed[0]; - vtev.offsetLocation(mScrollOffset[0], 0); - mNestedXOffset += mScrollOffset[0]; - } - if (!mIsBeingDragged && Math.abs(deltaX) > mTouchSlop) { - final ViewParent parent = getParent(); - if (parent != null) { - parent.requestDisallowInterceptTouchEvent(true); - } - mIsBeingDragged = true; - if (deltaX > 0) { - deltaX -= mTouchSlop; - } else { - deltaX += mTouchSlop; - } - } - if (mIsBeingDragged) { - // Scroll to follow the motion event - mLastMotionX = x - mScrollOffset[0]; - - final int oldX = getScrollX(); - final int range = getScrollRange(); - final int overscrollMode = ViewCompat.getOverScrollMode(this); - boolean canOverscroll = overscrollMode == ViewCompat.OVER_SCROLL_ALWAYS || - (overscrollMode == ViewCompat.OVER_SCROLL_IF_CONTENT_SCROLLS && - range > 0); - - // Calling overScrollByCompat will call onOverScrolled, which - // calls onScrollChanged if applicable. - if (overScrollByCompat(deltaX, 0, getScrollX(), 0, range, 0, 0, - 0, true) && !hasNestedScrollingParent()) { - // Break our velocity if we hit a scroll barrier. - mVelocityTracker.clear(); - } - - final int scrolledDeltaX = getScrollX() - oldX; - final int unconsumedX = deltaX - scrolledDeltaX; - if (dispatchNestedScroll(scrolledDeltaX, 0, unconsumedX, 0, mScrollOffset)) { - mLastMotionX -= mScrollOffset[0]; - vtev.offsetLocation(mScrollOffset[0], 0); - mNestedXOffset += mScrollOffset[0]; - } else if (canOverscroll) { - ensureGlows(); - final int pulledToX = oldX + deltaX; - if (pulledToX < 0) { - mEdgeGlowLeft.onPull((float) deltaX / getWidth(), - MotionEventCompat.getY(ev, activePointerIndex) / getHeight()); - if (!mEdgeGlowRight.isFinished()) { - mEdgeGlowRight.onRelease(); - } - } else if (pulledToX > range) { - mEdgeGlowRight.onPull((float) deltaX / getWidth(), - 1.f - MotionEventCompat.getY(ev, activePointerIndex) - / getHeight()); - if (!mEdgeGlowLeft.isFinished()) { - mEdgeGlowLeft.onRelease(); - } - } - if (mEdgeGlowLeft != null - && (!mEdgeGlowLeft.isFinished() || !mEdgeGlowRight.isFinished())) { - ViewCompat.postInvalidateOnAnimation(this); - } - } - } - break; - case MotionEvent.ACTION_UP: - if (mIsBeingDragged) { - final VelocityTracker velocityTracker = mVelocityTracker; - velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity); - int initialVelocity = (int) VelocityTrackerCompat.getXVelocity(velocityTracker, - mActivePointerId); - - if ((Math.abs(initialVelocity) > mMinimumVelocity)) { - flingWithNestedDispatch(-initialVelocity); - } - - mActivePointerId = INVALID_POINTER; - endDrag(); - } - break; - case MotionEvent.ACTION_CANCEL: - if (mIsBeingDragged && getChildCount() > 0) { - mActivePointerId = INVALID_POINTER; - endDrag(); - } - break; - case MotionEventCompat.ACTION_POINTER_DOWN: { - final int index = MotionEventCompat.getActionIndex(ev); - mLastMotionX = (int) MotionEventCompat.getX(ev, index); - mActivePointerId = MotionEventCompat.getPointerId(ev, index); - break; - } - case MotionEventCompat.ACTION_POINTER_UP: - onSecondaryPointerUp(ev); - mLastMotionX = (int) MotionEventCompat.getX(ev, - MotionEventCompat.findPointerIndex(ev, mActivePointerId)); - break; - } - - if (mVelocityTracker != null) { - mVelocityTracker.addMovement(vtev); - } - vtev.recycle(); - return true; - } - - private void onSecondaryPointerUp(MotionEvent ev) { - final int pointerIndex = (ev.getAction() & MotionEventCompat.ACTION_POINTER_INDEX_MASK) >> - MotionEventCompat.ACTION_POINTER_INDEX_SHIFT; - final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex); - if (pointerId == mActivePointerId) { - // This was our active pointer going up. Choose a new - // active pointer and adjust accordingly. - // TODO: Make this decision more intelligent. - final int newPointerIndex = pointerIndex == 0 ? 1 : 0; - mLastMotionX = (int) MotionEventCompat.getX(ev, newPointerIndex); - mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex); - if (mVelocityTracker != null) { - mVelocityTracker.clear(); - } - } - } - - public boolean onGenericMotionEvent(MotionEvent event) { - if ((MotionEventCompat.getSource(event) & InputDeviceCompat.SOURCE_CLASS_POINTER) != 0) { - switch (event.getAction()) { - case MotionEventCompat.ACTION_SCROLL: { - if (!mIsBeingDragged) { - final float hscroll = MotionEventCompat.getAxisValue(event, - MotionEventCompat.AXIS_HSCROLL); - if (hscroll != 0) { - final int delta = (int) (hscroll * getHorizontalScrollFactorCompat()); - final int range = getScrollRange(); - int oldScrollX = getScrollX(); - int newScrollX = oldScrollX - delta; - if (newScrollX < 0) { - newScrollX = 0; - } else if (newScrollX > range) { - newScrollX = range; - } - if (newScrollX != oldScrollX) { - super.scrollTo(newScrollX, getScrollY()); - return true; - } - } - } - } - } - } - return false; - } - - private float getHorizontalScrollFactorCompat() { - if (mHorizontalScrollFactor == 0) { - TypedValue outValue = new TypedValue(); - final Context context = getContext(); - if (!context.getTheme().resolveAttribute( - android.R.attr.listPreferredItemHeight, outValue, true)) { - throw new IllegalStateException( - "Expected theme to define listPreferredItemHeight."); - } - mHorizontalScrollFactor = outValue.getDimension( - context.getResources().getDisplayMetrics()); - } - return mHorizontalScrollFactor; - } - - protected void onOverScrolled(int scrollX, int scrollY, - boolean clampedX, boolean clampedY) { - super.scrollTo(scrollX, scrollY); - } - - boolean overScrollByCompat(int deltaX, int deltaY, - int scrollX, int scrollY, - int scrollRangeX, int scrollRangeY, - int maxOverScrollX, int maxOverScrollY, - boolean isTouchEvent) { - final int overScrollMode = ViewCompat.getOverScrollMode(this); - final boolean canScrollHorizontal = - computeHorizontalScrollRange() > computeHorizontalScrollExtent(); - final boolean canScrollVertical = - computeVerticalScrollRange() > computeVerticalScrollExtent(); - final boolean overScrollHorizontal = overScrollMode == ViewCompat.OVER_SCROLL_ALWAYS || - (overScrollMode == ViewCompat.OVER_SCROLL_IF_CONTENT_SCROLLS && canScrollHorizontal); - final boolean overScrollVertical = overScrollMode == ViewCompat.OVER_SCROLL_ALWAYS || - (overScrollMode == ViewCompat.OVER_SCROLL_IF_CONTENT_SCROLLS && canScrollVertical); - - int newScrollX = scrollX + deltaX; - if (!overScrollHorizontal) { - maxOverScrollX = 0; - } - - int newScrollY = scrollY + deltaY; - if (!overScrollVertical) { - maxOverScrollY = 0; - } - - // Clamp values if at the limits and record - final int left = -maxOverScrollX; - final int right = maxOverScrollX + scrollRangeX; - final int top = -maxOverScrollY; - final int bottom = maxOverScrollY + scrollRangeY; - - boolean clampedX = false; - if (newScrollX > right) { - newScrollX = right; - clampedX = true; - } else if (newScrollX < left) { - newScrollX = left; - clampedX = true; - } - - boolean clampedY = false; - if (newScrollY > bottom) { - newScrollY = bottom; - clampedY = true; - } else if (newScrollY < top) { - newScrollY = top; - clampedY = true; - } - - onOverScrolled(newScrollX, newScrollY, clampedX, clampedY); - - return clampedX || clampedY; - } - - private int getScrollRange() { - int scrollRange = 0; - if (getChildCount() > 0) { - View child = getChildAt(0); - scrollRange = Math.max(0, - child.getWidth() - (getWidth() - getPaddingLeft() - getPaddingRight())); - } - return scrollRange; - } - - /** - *

- * Finds the next focusable component that fits in the specified bounds. - *

- * - * @param topFocus look for a candidate is the one at the top of the bounds - * if topFocus is true, or at the bottom of the bounds if topFocus is - * false - * @param left the left offset of the bounds in which a focusable must be - * found - * @param right the right offset of the bounds in which a focusable must - * be found - * @return the next focusable component in the bounds or null if none can - * be found - */ - private View findFocusableViewInBounds(boolean topFocus, int left, int right) { - - List focusables = getFocusables(View.FOCUS_FORWARD); - View focusCandidate = null; - - /* - * A fully contained focusable is one where its top is below the bound's - * top, and its bottom is above the bound's bottom. A partially - * contained focusable is one where some part of it is within the - * bounds, but it also has some part that is not within bounds. A fully contained - * focusable is preferred to a partially contained focusable. - */ - boolean foundFullyContainedFocusable = false; - - int count = focusables.size(); - for (int i = 0; i < count; i++) { - View view = focusables.get(i); - int viewLeft = view.getLeft(); - int viewRight = view.getRight(); - - if (left < viewRight && viewLeft < right) { - /* - * the focusable is in the target area, it is a candidate for - * focusing - */ - - final boolean viewIsFullyContained = (left < viewLeft) && - (viewRight < right); - - if (focusCandidate == null) { - /* No candidate, take this one */ - focusCandidate = view; - foundFullyContainedFocusable = viewIsFullyContained; - } else { - final boolean viewIsCloserToBoundary = - (topFocus && viewLeft < focusCandidate.getLeft()) || - (!topFocus && viewRight > focusCandidate - .getRight()); - - if (foundFullyContainedFocusable) { - if (viewIsFullyContained && viewIsCloserToBoundary) { - /* - * We're dealing with only fully contained views, so - * it has to be closer to the boundary to beat our - * candidate - */ - focusCandidate = view; - } - } else { - if (viewIsFullyContained) { - /* Any fully contained view beats a partially contained view */ - focusCandidate = view; - foundFullyContainedFocusable = true; - } else if (viewIsCloserToBoundary) { - /* - * Partially contained view beats another partially - * contained view if it's closer - */ - focusCandidate = view; - } - } - } - } - } - - return focusCandidate; - } - - /** - *

Handles scrolling in response to a "page up/down" shortcut press. This - * method will scroll the view by one page up or down and give the focus - * to the topmost/bottommost component in the new visible area. If no - * component is a good candidate for focus, this scrollview reclaims the - * focus.

- * - * @param direction the scroll direction: {@link android.view.View#FOCUS_UP} - * to go one page up or - * {@link android.view.View#FOCUS_RIGHT} to go one page down - * @return true if the key event is consumed by this method, false otherwise - */ - public boolean pageScroll(int direction) { - boolean toRight = direction == View.FOCUS_RIGHT; - int width = getWidth(); - - if (toRight) { - mTempRect.left = getScrollX() + width; - int count = getChildCount(); - if (count > 0) { - View view = getChildAt(count - 1); - if (mTempRect.left + width > view.getRight()) { - mTempRect.left = view.getRight() - width; - } - } - } else { - mTempRect.left = getScrollX() - width; - if (mTempRect.left < 0) { - mTempRect.left = 0; - } - } - mTempRect.right = mTempRect.left + width; - - return scrollAndFocus(direction, mTempRect.left, mTempRect.right); - } - - /** - *

Handles scrolling in response to a "home/end" shortcut press. This - * method will scroll the view to the top or bottom and give the focus - * to the topmost/bottommost component in the new visible area. If no - * component is a good candidate for focus, this scrollview reclaims the - * focus.

- * - * @param direction the scroll direction: {@link android.view.View#FOCUS_UP} - * to go the top of the view or - * {@link android.view.View#FOCUS_DOWN} to go the bottom - * @return true if the key event is consumed by this method, false otherwise - */ - public boolean fullScroll(int direction) { - boolean right = direction == View.FOCUS_RIGHT; - int width = getWidth(); - - mTempRect.left = 0; - mTempRect.right = width; - - if (right) { - int count = getChildCount(); - if (count > 0) { - View view = getChildAt(count - 1); - mTempRect.right = view.getRight() + getPaddingRight(); - mTempRect.left = mTempRect.right - width; - } - } - - return scrollAndFocus(direction, mTempRect.left, mTempRect.right); - } - - /** - *

Scrolls the view to make the area defined by top and - * bottom visible. This method attempts to give the focus - * to a component visible in this area. If no component can be focused in - * the new visible area, the focus is reclaimed by this ScrollView.

- * - * @param direction the scroll direction: {@link android.view.View#FOCUS_UP} - * to go upward, {@link android.view.View#FOCUS_DOWN} to downward - * @param left the left offset of the new area to be made visible - * @param right the right offset of the new area to be made visible - * @return true if the key event is consumed by this method, false otherwise - */ - private boolean scrollAndFocus(int direction, int left, int right) { - boolean handled = true; - - int width = getWidth(); - int containerLeft = getScrollX(); - int containerRight = containerLeft + width; - boolean toLeft = direction == View.FOCUS_LEFT; - - View newFocused = findFocusableViewInBounds(toLeft, left, right); - if (newFocused == null) { - newFocused = this; - } - - if (left >= containerLeft && right <= containerRight) { - handled = false; - } else { - int delta = toLeft ? (left - containerLeft) : (right - containerRight); - doScrollX(delta); - } - - if (newFocused != findFocus()) newFocused.requestFocus(direction); - - return handled; - } - - /** - * Handle scrolling in response to an up or down arrow click. - * - * @param direction The direction corresponding to the arrow key that was - * pressed - * @return True if we consumed the event, false otherwise - */ - public boolean arrowScroll(int direction) { - - View currentFocused = findFocus(); - if (currentFocused == this) currentFocused = null; - - View nextFocused = FocusFinder.getInstance().findNextFocus(this, currentFocused, direction); - - final int maxJump = getMaxScrollAmount(); - - if (nextFocused != null && isWithinDeltaOfScreen(nextFocused, maxJump, getHeight())) { - nextFocused.getDrawingRect(mTempRect); - offsetDescendantRectToMyCoords(nextFocused, mTempRect); - int scrollDelta = computeScrollDeltaToGetChildRectOnScreen(mTempRect); - doScrollX(scrollDelta); - nextFocused.requestFocus(direction); - } else { - // no new focus - int scrollDelta = maxJump; - - if (direction == View.FOCUS_LEFT && getScrollX() < scrollDelta) { - scrollDelta = getScrollX(); - } else if (direction == View.FOCUS_RIGHT) { - if (getChildCount() > 0) { - int daRight = getChildAt(0).getRight(); - int screenRight = getScrollX() + getWidth() - getPaddingRight(); - if (daRight - screenRight < maxJump) { - scrollDelta = daRight - screenRight; - } - } - } - if (scrollDelta == 0) { - return false; - } - doScrollX(direction == View.FOCUS_RIGHT ? scrollDelta : -scrollDelta); - } - - if (currentFocused != null && currentFocused.isFocused() - && isOffScreen(currentFocused)) { - // previously focused item still has focus and is off screen, give - // it up (take it back to ourselves) - // (also, need to temporarily force FOCUS_BEFORE_DESCENDANTS so we are - // sure to - // get it) - final int descendantFocusability = getDescendantFocusability(); // save - setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS); - requestFocus(); - setDescendantFocusability(descendantFocusability); // restore - } - return true; - } - - /** - * @return whether the descendant of this scroll view is scrolled off - * screen. - */ - private boolean isOffScreen(View descendant) { - return !isWithinDeltaOfScreen(descendant, 0, getWidth()); - } - - /** - * @return whether the descendant of this scroll view is within delta - * pixels of being on the screen. - */ - private boolean isWithinDeltaOfScreen(View descendant, int delta, int width) { - descendant.getDrawingRect(mTempRect); - offsetDescendantRectToMyCoords(descendant, mTempRect); - - return (mTempRect.right + delta) >= getScrollX() - && (mTempRect.left - delta) <= (getScrollX() + width); - } - - /** - * Smooth scroll by a X delta - * - * @param delta the number of pixels to scroll by on the X axis - */ - private void doScrollX(int delta) { - if (delta != 0) { - if (mSmoothScrollingEnabled) { - smoothScrollBy(delta, 0); - } else { - scrollBy(delta, 0); - } - } - } - - /** - * Like {@link View#scrollBy}, but scroll smoothly instead of immediately. - * - * @param dx the number of pixels to scroll by on the X axis - * @param dy the number of pixels to scroll by on the Y axis - */ - public final void smoothScrollBy(int dx, int dy) { - if (getChildCount() == 0) { - // Nothing to do. - return; - } - long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll; - if (duration > ANIMATED_SCROLL_GAP) { - final int width = getWidth() - getPaddingLeft() - getPaddingRight(); - final int right = getChildAt(0).getWidth(); - final int maxX = Math.max(0, right - width); - final int scrollX = getScrollX(); - dx = Math.max(0, Math.min(scrollX + dx, maxX)) - scrollX; - - mScroller.startScroll(scrollX, getScrollY(), dx, 0); - ViewCompat.postInvalidateOnAnimation(this); - } else { - if (!mScroller.isFinished()) { - mScroller.abortAnimation(); - } - scrollBy(dx, dy); - } - mLastScroll = AnimationUtils.currentAnimationTimeMillis(); - } - - /** - * Like {@link #scrollTo}, but scroll smoothly instead of immediately. - * - * @param x the position where to scroll on the X axis - * @param y the position where to scroll on the Y axis - */ - public final void smoothScrollTo(int x, int y) { - smoothScrollBy(x - getScrollX(), y - getScrollY()); - } - - @Override - protected int computeHorizontalScrollRange() { - final int count = getChildCount(); - final int contentWidth = getWidth() - getPaddingLeft() - getPaddingRight(); - if (count == 0) { - return contentWidth; - } - - int scrollRange = getChildAt(0).getRight(); - final int scrollX = getScrollX(); - final int overscrollRight = Math.max(0, scrollRange - contentWidth); - if (scrollX < 0) { - scrollRange -= scrollX; - } else if (scrollX > overscrollRight) { - scrollRange += scrollX - overscrollRight; - } - - return scrollRange; - } - - @Override - protected int computeHorizontalScrollOffset() { - return Math.max(0, super.computeHorizontalScrollOffset()); - } - - @Override - protected void measureChild(View child, int parentWidthMeasureSpec, int parentHeightMeasureSpec) { - ViewGroup.LayoutParams lp = child.getLayoutParams(); - - int childWidthMeasureSpec; - int childHeightMeasureSpec; - - childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, getPaddingTop() - + getPaddingBottom(), lp.height); - - childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED); - - child.measure(childWidthMeasureSpec, childHeightMeasureSpec); - } - - @Override - protected void measureChildWithMargins(View child, int parentWidthMeasureSpec, int widthUsed, - int parentHeightMeasureSpec, int heightUsed) { - final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams(); - - final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec, - getPaddingTop() + getPaddingBottom() + lp.topMargin + lp.bottomMargin - + heightUsed, lp.height); - final int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec( - lp.leftMargin + lp.rightMargin, MeasureSpec.UNSPECIFIED); - - child.measure(childWidthMeasureSpec, childHeightMeasureSpec); - } - - @Override - public void computeScroll() { - if (mScroller.computeScrollOffset()) { - int oldX = getScrollX(); - int oldY = getScrollY(); - int x = mScroller.getCurrX(); - int y = mScroller.getCurrY(); - - if (oldX != x || oldY != y) { - final int range = getScrollRange(); - final int overscrollMode = ViewCompat.getOverScrollMode(this); - final boolean canOverscroll = overscrollMode == ViewCompat.OVER_SCROLL_ALWAYS || - (overscrollMode == ViewCompat.OVER_SCROLL_IF_CONTENT_SCROLLS && range > 0); - - overScrollByCompat(x - oldX, y - oldY, oldX, oldY, range, 0, - 0, 0, false); - - if (canOverscroll) { - ensureGlows(); - if (x <= 0 && oldX > 0) { - mEdgeGlowLeft.onAbsorb((int) mScroller.getCurrVelocity()); - } else if (x >= range && oldX < range) { - mEdgeGlowRight.onAbsorb((int) mScroller.getCurrVelocity()); - } - } - } - } - } - - /** - * Scrolls the view to the given child. - * - * @param child the View to scroll to - */ - private void scrollToChild(View child) { - child.getDrawingRect(mTempRect); - - /* Offset from child's local coordinates to ScrollView coordinates */ - offsetDescendantRectToMyCoords(child, mTempRect); - - int scrollDelta = computeScrollDeltaToGetChildRectOnScreen(mTempRect); - - if (scrollDelta != 0) { - scrollBy(scrollDelta, 0); - } - } - - /** - * If rect is off screen, scroll just enough to get it (or at least the - * first screen size chunk of it) on screen. - * - * @param rect The rectangle. - * @param immediate True to scroll immediately without animation - * @return true if scrolling was performed - */ - private boolean scrollToChildRect(Rect rect, boolean immediate) { - final int delta = computeScrollDeltaToGetChildRectOnScreen(rect); - final boolean scroll = delta != 0; - if (scroll) { - if (immediate) { - scrollBy(delta, 0); - } else { - smoothScrollBy(delta, 0); - } - } - return scroll; - } - - /** - * Compute the amount to scroll in the Y direction in order to get - * a rectangle completely on the screen (or, if taller than the screen, - * at least the first screen size chunk of it). - * - * @param rect The rect. - * @return The scroll delta. - */ - protected int computeScrollDeltaToGetChildRectOnScreen(Rect rect) { - if (getChildCount() == 0) return 0; - - int width = getWidth(); - int screenLeft = getScrollX(); - int screenRight = screenLeft + width; - - int fadingEdge = getHorizontalFadingEdgeLength(); - - // leave room for top fading edge as long as rect isn't at very top - if (rect.left > 0) { - screenLeft += fadingEdge; - } - - // leave room for bottom fading edge as long as rect isn't at very bottom - if (rect.right < getChildAt(0).getWidth()) { - screenRight -= fadingEdge; - } - - int scrollXDelta = 0; - - if (rect.right > screenRight && rect.left > screenLeft) { - // need to move down to get it in view: move down just enough so - // that the entire rectangle is in view (or at least the first - // screen size chunk). - - if (rect.width() > width) { - // just enough to get screen size chunk on - scrollXDelta += (rect.left - screenLeft); - } else { - // get entire rect at bottom of screen - scrollXDelta += (rect.right - screenRight); - } - - // make sure we aren't scrolling beyond the end of our content - int right = getChildAt(0).getRight(); - int distanceToRight = right - screenRight; - scrollXDelta = Math.min(scrollXDelta, distanceToRight); - - } else if (rect.left < screenLeft && rect.right < screenRight) { - // need to move up to get it in view: move up just enough so that - // entire rectangle is in view (or at least the first screen - // size chunk of it). - - if (rect.width() > width) { - // screen size chunk - scrollXDelta -= (screenRight - rect.right); - } else { - // entire rect at top - scrollXDelta -= (screenLeft - rect.left); - } - - // make sure we aren't scrolling any further than the top our content - scrollXDelta = Math.max(scrollXDelta, -getScrollX()); - } - return scrollXDelta; - } - - @Override - public void requestChildFocus(View child, View focused) { - if (!mIsLayoutDirty) { - scrollToChild(focused); - } else { - // The child may not be laid out yet, we can't compute the scroll yet - mChildToScrollTo = focused; - } - super.requestChildFocus(child, focused); - } - - - /** - * When looking for focus in children of a scroll view, need to be a little - * more careful not to give focus to something that is scrolled off screen. - * - * This is more expensive than the default {@link android.view.ViewGroup} - * implementation, otherwise this behavior might have been made the default. - */ - @Override - protected boolean onRequestFocusInDescendants(int direction, - Rect previouslyFocusedRect) { - - // convert from forward / backward notation to up / down / left / right - // (ugh). - if (direction == View.FOCUS_FORWARD) { - direction = View.FOCUS_RIGHT; - } else if (direction == View.FOCUS_BACKWARD) { - direction = View.FOCUS_LEFT; - } - - final View nextFocus = previouslyFocusedRect == null ? - FocusFinder.getInstance().findNextFocus(this, null, direction) : - FocusFinder.getInstance().findNextFocusFromRect(this, - previouslyFocusedRect, direction); - - if (nextFocus == null) { - return false; - } - - if (isOffScreen(nextFocus)) { - return false; - } - - return nextFocus.requestFocus(direction, previouslyFocusedRect); - } - - @Override - public boolean requestChildRectangleOnScreen(View child, Rect rectangle, - boolean immediate) { - // offset into coordinate space of this scroll view - rectangle.offset(child.getLeft() - child.getScrollX(), - child.getTop() - child.getScrollY()); - - return scrollToChildRect(rectangle, immediate); - } - - @Override - public void requestLayout() { - mIsLayoutDirty = true; - super.requestLayout(); - } - - @Override - protected void onLayout(boolean changed, int l, int t, int r, int b) { - super.onLayout(changed, l, t, r, b); - mIsLayoutDirty = false; - // Give a child focus if it needs it - if (mChildToScrollTo != null && isViewDescendantOf(mChildToScrollTo, this)) { - scrollToChild(mChildToScrollTo); - } - mChildToScrollTo = null; - - if (!mIsLaidOut) { - if (mSavedState != null) { - scrollTo(mSavedState.scrollPosition, getScrollY()); - mSavedState = null; - } // mScrollY default value is "0" - - final int childWidth = (getChildCount() > 0) ? getChildAt(0).getMeasuredWidth() : 0; - final int scrollRange = Math.max(0, - childWidth - (r - l - getPaddingRight() - getPaddingLeft())); - - // Don't forget to clamp - if (getScrollX() > scrollRange) { - scrollTo(scrollRange, getScrollY()); - } else if (getScrollX() < 0) { - scrollTo(0, getScrollY()); - } - } - - // Calling this with the present values causes it to re-claim them - scrollTo(getScrollX(), getScrollY()); - mIsLaidOut = true; - } - - @SuppressLint("MissingSuperCall") - @Override - public void onAttachedToWindow() { - mIsLaidOut = false; - } - - @Override - protected void onSizeChanged(int w, int h, int oldw, int oldh) { - super.onSizeChanged(w, h, oldw, oldh); - - View currentFocused = findFocus(); - if (null == currentFocused || this == currentFocused) - return; - - // If the currently-focused view was visible on the screen when the - // screen was at the old height, then scroll the screen to make that - // view visible with the new screen height. - if (isWithinDeltaOfScreen(currentFocused, 0, oldw)) { - currentFocused.getDrawingRect(mTempRect); - offsetDescendantRectToMyCoords(currentFocused, mTempRect); - int scrollDelta = computeScrollDeltaToGetChildRectOnScreen(mTempRect); - doScrollX(scrollDelta); - } - } - - /** - * Return true if child is a descendant of parent, (or equal to the parent). - */ - private static boolean isViewDescendantOf(View child, View parent) { - if (child == parent) { - return true; - } - - final ViewParent theParent = child.getParent(); - return (theParent instanceof ViewGroup) && isViewDescendantOf((View) theParent, parent); - } - - /** - * Fling the scroll view - * - * @param velocityX The initial velocity in the X direction. Positive - * numbers mean that the finger/cursor is moving down the screen, - * which means we want to scroll towards the top. - */ - public void fling(int velocityX) { - if (getChildCount() > 0) { - int width = getWidth() - getPaddingRight() - getPaddingLeft(); - int right = getChildAt(0).getWidth(); - - mScroller.fling(getScrollX(), getScrollY(), velocityX, 0, 0, Math.max(0, right - width) - , 0, - 0, width/2, 0); - - ViewCompat.postInvalidateOnAnimation(this); - } - } - - private void flingWithNestedDispatch(int velocityX) { - final int scrollX = getScrollX(); - final boolean canFling = (scrollX > 0 || velocityX > 0) && - (scrollX < getScrollRange() || velocityX < 0); - if (!dispatchNestedPreFling(velocityX, 0)) { - dispatchNestedFling(velocityX, 0, canFling); - if (canFling) { - fling(velocityX); - } - } - } - - private void endDrag() { - mIsBeingDragged = false; - - recycleVelocityTracker(); - stopNestedScroll(); - - if (mEdgeGlowLeft != null) { - mEdgeGlowLeft.onRelease(); - mEdgeGlowRight.onRelease(); - } - } - - /** - * {@inheritDoc} - * - *

This version also clamps the scrolling to the bounds of our child. - */ - @Override - public void scrollTo(int x, int y) { - // we rely on the fact the View.scrollBy calls scrollTo. - if (getChildCount() > 0) { - View child = getChildAt(0); - x = clamp(x, getWidth() - getPaddingRight() - getPaddingLeft(), child.getWidth()); - y = clamp(y, getHeight() - getPaddingBottom() - getPaddingTop(), child.getHeight()); - if (x != getScrollX() || y != getScrollY()) { - super.scrollTo(x, y); - } - } - } - - private void ensureGlows() { - if (ViewCompat.getOverScrollMode(this) != ViewCompat.OVER_SCROLL_NEVER) { - if (mEdgeGlowLeft == null) { - Context context = getContext(); - mEdgeGlowLeft = new EdgeEffectCompat(context); - mEdgeGlowRight = new EdgeEffectCompat(context); - - } - } else { - mEdgeGlowLeft = null; - mEdgeGlowRight = null; - } - } - - @Override - public void draw(Canvas canvas) { - super.draw(canvas); - if (mEdgeGlowLeft != null) { - final int scrollX = getScrollX(); - if (!mEdgeGlowLeft.isFinished()) { - final int restoreCount = canvas.save(); - final int height = getHeight() - getPaddingTop() - getPaddingBottom(); - - canvas.translate(Math.min(0, scrollX), getPaddingTop()); - canvas.rotate(90, 0, height); - mEdgeGlowLeft.setSize(getWidth(), height); - if (mEdgeGlowLeft.draw(canvas)) { - ViewCompat.postInvalidateOnAnimation(this); - } - canvas.restoreToCount(restoreCount); - } - if (!mEdgeGlowRight.isFinished()) { - final int restoreCount = canvas.save(); - final int width = getWidth(); - final int height = getHeight() - getPaddingTop() - getPaddingBottom(); - - canvas.translate(Math.max(getScrollRange(), scrollX) + width, - -height + getPaddingTop()); - canvas.rotate(90, 0, height); - mEdgeGlowRight.setSize(width, height); - if (mEdgeGlowRight.draw(canvas)) { - ViewCompat.postInvalidateOnAnimation(this); - } - canvas.restoreToCount(restoreCount); - } - } - } - - private static int clamp(int n, int my, int child) { - if (my >= child || n < 0) { - /* my >= child is this case: - * |--------------- me ---------------| - * |------ child ------| - * or - * |--------------- me ---------------| - * |------ child ------| - * or - * |--------------- me ---------------| - * |------ child ------| - * - * n < 0 is this case: - * |------ me ------| - * |-------- child --------| - * |-- mScrollX --| - */ - return 0; - } - if ((my+n) > child) { - /* this case: - * |------ me ------| - * |------ child ------| - * |-- mScrollX --| - */ - return child-my; - } - return n; - } - - @Override - protected void onRestoreInstanceState(Parcelable state) { - SavedState ss = (SavedState) state; - super.onRestoreInstanceState(ss.getSuperState()); - mSavedState = ss; - requestLayout(); - } - - @Override - protected Parcelable onSaveInstanceState() { - Parcelable superState = super.onSaveInstanceState(); - SavedState ss = new SavedState(superState); - ss.scrollPosition = getScrollX(); - return ss; - } - - static class SavedState extends BaseSavedState { - public int scrollPosition; - - SavedState(Parcelable superState) { - super(superState); - } - - public SavedState(Parcel source) { - super(source); - scrollPosition = source.readInt(); - } - - @Override - public void writeToParcel(Parcel dest, int flags) { - super.writeToParcel(dest, flags); - dest.writeInt(scrollPosition); - } - - @Override - public String toString() { - return "HorizontalScrollView.SavedState{" - + Integer.toHexString(System.identityHashCode(this)) - + " scrollPosition=" + scrollPosition + "}"; - } - - public static final Parcelable.Creator CREATOR - = new Parcelable.Creator() { - public SavedState createFromParcel(Parcel in) { - return new SavedState(in); - } - - public SavedState[] newArray(int size) { - return new SavedState[size]; - } - }; - } - - static class AccessibilityDelegate extends AccessibilityDelegateCompat { - @Override - public boolean performAccessibilityAction(View host, int action, Bundle arguments) { - if (super.performAccessibilityAction(host, action, arguments)) { - return true; - } - final NestHorizontalScrollView nsvHost = (NestHorizontalScrollView) host; - if (!nsvHost.isEnabled()) { - return false; - } - switch (action) { - case AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD: { - final int viewportWidth = nsvHost.getWidth() - nsvHost.getPaddingRight() - - nsvHost.getPaddingLeft(); - final int targetScrollX = Math.min(nsvHost.getScrollX() + viewportWidth, - nsvHost.getScrollRange()); - if (targetScrollX != nsvHost.getScrollX()) { - nsvHost.smoothScrollTo(targetScrollX, 0); - return true; - } - } - return false; - case AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD: { - final int viewportWidth = nsvHost.getWidth() - nsvHost.getPaddingRight() - - nsvHost.getPaddingLeft(); - final int targetScrollX = Math.max(nsvHost.getScrollX() - viewportWidth, 0); - if (targetScrollX != nsvHost.getScrollX()) { - nsvHost.smoothScrollTo(0, targetScrollX); - return true; - } - } - return false; - } - return false; - } - - @Override - public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) { - super.onInitializeAccessibilityNodeInfo(host, info); - final NestHorizontalScrollView nsvHost = (NestHorizontalScrollView) host; - info.setClassName(ScrollView.class.getName()); - if (nsvHost.isEnabled()) { - final int scrollRange = nsvHost.getScrollRange(); - if (scrollRange > 0) { - info.setScrollable(true); - if (nsvHost.getScrollX() > 0) { - info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD); - } - if (nsvHost.getScrollX() < scrollRange) { - info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD); - } - } - } - } - - @Override - public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) { - super.onInitializeAccessibilityEvent(host, event); - final NestHorizontalScrollView nsvHost = (NestHorizontalScrollView) host; - event.setClassName(ScrollView.class.getName()); - final AccessibilityRecordCompat record = AccessibilityEventCompat.asRecord(event); - final boolean scrollable = nsvHost.getScrollRange() > 0; - record.setScrollable(scrollable); - record.setScrollX(nsvHost.getScrollX()); - record.setScrollY(nsvHost.getScrollY()); - record.setMaxScrollX(nsvHost.getScrollRange()); - record.setMaxScrollY(nsvHost.getScrollY()); - } - } -} diff --git a/app/src/main/java/com/loopeer/codereader/ui/view/NestedScrollWebView.java b/app/src/main/java/com/loopeer/codereader/ui/view/NestedScrollWebView.java deleted file mode 100644 index 3d888fa..0000000 --- a/app/src/main/java/com/loopeer/codereader/ui/view/NestedScrollWebView.java +++ /dev/null @@ -1,169 +0,0 @@ -package com.loopeer.codereader.ui.view; - -import android.content.Context; -import android.support.v4.view.MotionEventCompat; -import android.support.v4.view.NestedScrollingChild; -import android.support.v4.view.NestedScrollingChildHelper; -import android.support.v4.view.ViewCompat; -import android.util.AttributeSet; -import android.view.MotionEvent; -import android.webkit.WebView; - -/* -* https://github.com/rhlff/NestedScrollWebView -* */ -public class NestedScrollWebView extends WebView implements NestedScrollingChild { - - public interface ScrollChangeListener{ - void onScrollChanged(int l, int t, int oldl, int oldt); - } - - public static final String TAG = NestedScrollWebView.class.getSimpleName(); - - private int mLastMotionY; - - private final int[] mScrollOffset = new int[2]; - private final int[] mScrollConsumed = new int[2]; - - private int mNestedYOffset; - - private NestedScrollingChildHelper mChildHelper; - - private ScrollChangeListener mScrollChangeListener; - - public NestedScrollWebView(Context context) { - super(context); - init(); - } - - public NestedScrollWebView(Context context, AttributeSet attrs) { - super(context, attrs); - init(); - } - - public NestedScrollWebView(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - init(); - } - - private void init() { - mChildHelper = new NestedScrollingChildHelper(this); - setNestedScrollingEnabled(true); - } - - @Override - public boolean onTouchEvent(MotionEvent event) { - boolean result = false; - - MotionEvent trackedEvent = MotionEvent.obtain(event); - - final int action = MotionEventCompat.getActionMasked(event); - - if (action == MotionEvent.ACTION_DOWN) { - mNestedYOffset = 0; - } - - int y = (int) event.getY(); - - event.offsetLocation(0, mNestedYOffset); - - switch (action) { - case MotionEvent.ACTION_DOWN: - mLastMotionY = y; - startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL); - result = super.onTouchEvent(event); - break; - case MotionEvent.ACTION_MOVE: - int deltaY = mLastMotionY - y; - - if (dispatchNestedPreScroll(0, deltaY, mScrollConsumed, mScrollOffset)) { - deltaY -= mScrollConsumed[1]; - trackedEvent.offsetLocation(0, mScrollOffset[1]); - mNestedYOffset += mScrollOffset[1]; - } - - int oldY = getScrollY(); - mLastMotionY = y - mScrollOffset[1]; - if (deltaY < 0) { - int newScrollY = Math.max(0, oldY + deltaY); - deltaY -= newScrollY - oldY; - if (dispatchNestedScroll(0, newScrollY - deltaY, 0, deltaY, mScrollOffset)) { - mLastMotionY -= mScrollOffset[1]; - trackedEvent.offsetLocation(0, mScrollOffset[1]); - mNestedYOffset += mScrollOffset[1]; - } - } - - trackedEvent.recycle(); - result = super.onTouchEvent(trackedEvent); - break; - case MotionEvent.ACTION_POINTER_DOWN: - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_CANCEL: - stopNestedScroll(); - result = super.onTouchEvent(event); - break; - } - return result; - } - - // NestedScrollingChild - - @Override - public void setNestedScrollingEnabled(boolean enabled) { - mChildHelper.setNestedScrollingEnabled(enabled); - } - - @Override - public boolean isNestedScrollingEnabled() { - return mChildHelper.isNestedScrollingEnabled(); - } - - @Override - public boolean startNestedScroll(int axes) { - return mChildHelper.startNestedScroll(axes); - } - - @Override - public void stopNestedScroll() { - mChildHelper.stopNestedScroll(); - } - - @Override - public boolean hasNestedScrollingParent() { - return mChildHelper.hasNestedScrollingParent(); - } - - @Override - public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed, int dyUnconsumed, int[] offsetInWindow) { - return mChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow); - } - - @Override - public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) { - return mChildHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow); - } - - @Override - public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) { - return mChildHelper.dispatchNestedFling(velocityX, velocityY, consumed); - } - - @Override - public boolean dispatchNestedPreFling(float velocityX, float velocityY) { - return mChildHelper.dispatchNestedPreFling(velocityX, velocityY); - } - - public void setScrollChangeListener(ScrollChangeListener scrollChangeListener) { - mScrollChangeListener = scrollChangeListener; - } - - @Override - protected void onScrollChanged(int l, int t, int oldl, int oldt) { - super.onScrollChanged(l, t, oldl, oldt); - if (mScrollChangeListener != null) { - mScrollChangeListener.onScrollChanged(l, t, oldl, oldt); - } - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/loopeer/codereader/ui/view/ProgressIndicatorView.java b/app/src/main/java/com/loopeer/codereader/ui/view/ProgressIndicatorView.java deleted file mode 100644 index 8bee473..0000000 --- a/app/src/main/java/com/loopeer/codereader/ui/view/ProgressIndicatorView.java +++ /dev/null @@ -1,195 +0,0 @@ -package com.loopeer.codereader.ui.view; - -import android.animation.Animator; -import android.animation.ValueAnimator; -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.RectF; -import android.support.v4.content.ContextCompat; -import android.util.AttributeSet; -import android.view.View; - -import com.loopeer.codereader.R; - -import java.util.ArrayList; -import java.util.List; - -public class ProgressIndicatorView extends View { - - float scaleFloat1, scaleFloat2, degrees; - public static final int DEFAULT_SIZE = 45; - - int mIndicatorColor; - Paint mPaint; - private List mAnimators; - private boolean mHasAnimation; - - public ProgressIndicatorView(Context context) { - this(context, null); - } - - public ProgressIndicatorView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public ProgressIndicatorView(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - init(context, attrs, defStyleAttr); - } - - private void init(Context context, AttributeSet attrs, int defStyleAttr) { - mIndicatorColor = ContextCompat.getColor(getContext(), R.color.colorPrimary); - mPaint = new Paint(); - mPaint.setColor(mIndicatorColor); - mPaint.setStyle(Paint.Style.FILL); - mPaint.setAntiAlias(true); - } - - @Override - protected void onAttachedToWindow() { - super.onAttachedToWindow(); - setAnimationStatus(AnimStatus.START); - } - - @Override - protected void onDetachedFromWindow() { - super.onDetachedFromWindow(); - setAnimationStatus(AnimStatus.CANCEL); - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - int width = measureDimension(dp2px(DEFAULT_SIZE), widthMeasureSpec); - int height = measureDimension(dp2px(DEFAULT_SIZE), heightMeasureSpec); - setMeasuredDimension(width, height); - } - - private int dp2px(int dpValue) { - return (int) getContext().getResources().getDisplayMetrics().density * dpValue; - } - - private int measureDimension(int defaultSize, int measureSpec) { - int result = defaultSize; - int specMode = MeasureSpec.getMode(measureSpec); - int specSize = MeasureSpec.getSize(measureSpec); - if (specMode == MeasureSpec.EXACTLY) { - result = specSize; - } else if (specMode == MeasureSpec.AT_MOST) { - result = Math.min(defaultSize, specSize); - } else { - result = defaultSize; - } - return result; - } - - @Override - protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - super.onLayout(changed, left, top, right, bottom); - if (!mHasAnimation) { - mHasAnimation = true; - initAnimation(); - } - } - - @Override - protected void onDraw(Canvas canvas) { - super.onDraw(canvas); - float circleSpacing = 12; - float x = getWidth() / 2; - float y = getHeight() / 2; - - canvas.save(); - canvas.translate(x, y); - canvas.scale(scaleFloat1, scaleFloat1); - mPaint.setStyle(Paint.Style.FILL); - canvas.drawCircle(0, 0, x / 2.5f, mPaint); - - canvas.restore(); - - canvas.translate(x, y); - canvas.scale(scaleFloat2, scaleFloat2); - canvas.rotate(degrees); - - mPaint.setStrokeWidth(3); - mPaint.setStyle(Paint.Style.STROKE); - - float[] startAngles = new float[]{225, 45}; - for (int i = 0; i < 2; i++) { - RectF rectF = new RectF(-x + circleSpacing, -y + circleSpacing, x - circleSpacing, y - circleSpacing); - canvas.drawArc(rectF, startAngles[i], 90, false, mPaint); - } - } - - public void initAnimation() { - mAnimators = createAnimation(); - } - - public void setAnimationStatus(AnimStatus animStatus) { - if (mAnimators == null) { - return; - } - int count = mAnimators.size(); - for (int i = 0; i < count; i++) { - Animator animator = mAnimators.get(i); - boolean isRunning = animator.isRunning(); - switch (animStatus) { - case START: - if (!isRunning) { - animator.start(); - } - break; - case END: - if (isRunning) { - animator.end(); - } - break; - case CANCEL: - if (isRunning) { - animator.cancel(); - } - break; - } - } - } - - public enum AnimStatus { - START, END, CANCEL - } - - public List createAnimation() { - ValueAnimator scaleAnim = ValueAnimator.ofFloat(1, 0.3f, 1); - scaleAnim.setDuration(1000); - scaleAnim.setRepeatCount(-1); - scaleAnim.addUpdateListener(animation -> { - scaleFloat1 = (float) animation.getAnimatedValue(); - postInvalidate(); - }); - scaleAnim.start(); - - ValueAnimator scaleAnim2 = ValueAnimator.ofFloat(1, 0.6f, 1); - scaleAnim2.setDuration(1000); - scaleAnim2.setRepeatCount(-1); - scaleAnim2.addUpdateListener(animation -> { - scaleFloat2 = (float) animation.getAnimatedValue(); - postInvalidate(); - }); - scaleAnim2.start(); - - ValueAnimator rotateAnim = ValueAnimator.ofFloat(0, 180, 360); - rotateAnim.setDuration(1000); - rotateAnim.setRepeatCount(-1); - rotateAnim.addUpdateListener(animation -> { - degrees = (float) animation.getAnimatedValue(); - postInvalidate(); - }); - rotateAnim.start(); - - List animators = new ArrayList<>(); - animators.add(scaleAnim); - animators.add(scaleAnim2); - animators.add(rotateAnim); - return animators; - } -} diff --git a/app/src/main/java/com/loopeer/codereader/ui/view/ProgressLoading.java b/app/src/main/java/com/loopeer/codereader/ui/view/ProgressLoading.java deleted file mode 100644 index 21af7ac..0000000 --- a/app/src/main/java/com/loopeer/codereader/ui/view/ProgressLoading.java +++ /dev/null @@ -1,83 +0,0 @@ -package com.loopeer.codereader.ui.view; - -import android.app.Dialog; -import android.content.Context; -import android.graphics.drawable.ColorDrawable; -import android.text.TextUtils; -import android.view.View; -import android.view.Window; -import android.widget.TextView; - -import com.loopeer.codereader.R; - -public class ProgressLoading extends Dialog { - - private View mProgressView; - private TextView mMessageTextView; - - private Window mWindow; - - private CharSequence mMessage; - private boolean mShowProgress = true; - - public ProgressLoading(Context context, int theme) { - super(context, theme); - initialize(context, theme); - } - - private void initialize(Context context, int theme) { - mWindow = getWindow(); - mWindow.requestFeature(Window.FEATURE_NO_TITLE); - mWindow.setBackgroundDrawable(new ColorDrawable(android.graphics.Color.TRANSPARENT)); - setContentView(R.layout.progress_loading); - mMessageTextView = (TextView) findViewById(R.id.text_message); - mProgressView = findViewById(R.id.progress); - } - - public ProgressLoading setMessage(int resId) { - return setMessage(getContext().getString(resId)); - } - - public ProgressLoading setMessage(CharSequence msg) { - mMessage = msg; - return this; - } - - public ProgressLoading updateMessage(CharSequence msg) { - mMessage = msg; - show(); - return this; - } - - public ProgressLoading hideMessage() { - return updateMessage(""); - } - - public ProgressLoading hideProgressBar() { - mShowProgress = false; - show(); - return this; - } - - @Override - public void show() { - if (mMessageTextView != null) { - if (TextUtils.isEmpty(mMessage)) { - mMessageTextView.setVisibility(View.GONE); - } else { - mMessageTextView.setVisibility(View.VISIBLE); - mMessageTextView.setText(mMessage); - } - } - - if (mProgressView != null) { - if (mShowProgress) { - mProgressView.setVisibility(View.VISIBLE); - } else { - mProgressView.setVisibility(View.GONE); - } - } - super.show(); - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/loopeer/codereader/ui/view/ProgressWebView.java b/app/src/main/java/com/loopeer/codereader/ui/view/ProgressWebView.java deleted file mode 100644 index 84fe270..0000000 --- a/app/src/main/java/com/loopeer/codereader/ui/view/ProgressWebView.java +++ /dev/null @@ -1,53 +0,0 @@ -package com.loopeer.codereader.ui.view; - -import android.content.Context; -import android.support.v4.content.ContextCompat; -import android.util.AttributeSet; -import android.webkit.WebView; -import android.widget.ProgressBar; - -import com.loopeer.codereader.R; - -public class ProgressWebView extends WebView { - - private ProgressBar mProgressBar; - - public ProgressWebView(Context context, AttributeSet attrs) { - super(context, attrs); - mProgressBar = new ProgressBar(context, null, android.R.attr.progressBarStyleHorizontal); - mProgressBar.setIndeterminate(false); - mProgressBar.setProgressDrawable(ContextCompat.getDrawable(context, R.drawable.progress_horizontal_web_view)); - mProgressBar.setIndeterminateDrawable(ContextCompat.getDrawable(context, android.R.drawable.progress_indeterminate_horizontal)); - mProgressBar.setLayoutParams(new LayoutParams(LayoutParams.MATCH_PARENT, 5, 0, 0)); - mProgressBar.setMinimumHeight(16); - addView(mProgressBar); - setWebChromeClient(new WebChromeClient()); - } - - public void setProgressbarGone() { - mProgressBar.setVisibility(GONE); - } - - public class WebChromeClient extends android.webkit.WebChromeClient { - @Override - public void onProgressChanged(WebView view, int newProgress) { - if (newProgress == 100) { - mProgressBar.setVisibility(GONE); - } else { - if (mProgressBar.getVisibility() == GONE) mProgressBar.setVisibility(VISIBLE); - mProgressBar.setProgress(newProgress); - } - super.onProgressChanged(view, newProgress); - } - - } - - @Override - protected void onScrollChanged(int l, int t, int oldl, int oldt) { - LayoutParams lp = (LayoutParams) mProgressBar.getLayoutParams(); - lp.x = l; - lp.y = t; - mProgressBar.setLayoutParams(lp); - super.onScrollChanged(l, t, oldl, oldt); - } -} diff --git a/app/src/main/java/com/loopeer/codereader/ui/view/ScrollAwareFABBehavior.java b/app/src/main/java/com/loopeer/codereader/ui/view/ScrollAwareFABBehavior.java deleted file mode 100644 index dd05f60..0000000 --- a/app/src/main/java/com/loopeer/codereader/ui/view/ScrollAwareFABBehavior.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.loopeer.codereader.ui.view; - -import android.content.Context; -import android.support.design.widget.CoordinatorLayout; -import android.support.design.widget.FloatingActionButton; -import android.support.v4.view.ViewCompat; -import android.util.AttributeSet; -import android.view.View; - -public class ScrollAwareFABBehavior extends FloatingActionButton.Behavior { - public ScrollAwareFABBehavior(Context context, AttributeSet attrs) { - super(); - } - - @Override - public boolean onStartNestedScroll(final CoordinatorLayout coordinatorLayout, final FloatingActionButton child, - final View directTargetChild, final View target, final int nestedScrollAxes) { - // Ensure we react to vertical scrolling - return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL - || super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, nestedScrollAxes); - } - - @Override - public void onNestedScroll(final CoordinatorLayout coordinatorLayout, final FloatingActionButton child, - final View target, final int dxConsumed, final int dyConsumed, - final int dxUnconsumed, final int dyUnconsumed) { - super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed); - if (dyConsumed > 0 && child.getVisibility() == View.VISIBLE) { - // User scrolled down and the FAB is currently visible -> hide the FAB - child.hide(); - } else if (dyConsumed < 0 && child.getVisibility() != View.VISIBLE) { - // User scrolled up and the FAB is currently not visible -> show the FAB - child.show(); - } - } -} \ No newline at end of file diff --git a/app/src/main/java/com/loopeer/codereader/ui/view/TextWatcherImpl.java b/app/src/main/java/com/loopeer/codereader/ui/view/TextWatcherImpl.java deleted file mode 100644 index 856dfb2..0000000 --- a/app/src/main/java/com/loopeer/codereader/ui/view/TextWatcherImpl.java +++ /dev/null @@ -1,22 +0,0 @@ -package com.loopeer.codereader.ui.view; - -import android.text.Editable; -import android.text.TextWatcher; - -public class TextWatcherImpl implements TextWatcher { - - @Override - public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) { - - } - - @Override - public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) { - - } - - @Override - public void afterTextChanged(Editable editable) { - - } -} diff --git a/app/src/main/java/com/loopeer/codereader/ui/view/ThemeChooser.java b/app/src/main/java/com/loopeer/codereader/ui/view/ThemeChooser.java deleted file mode 100644 index a2833aa..0000000 --- a/app/src/main/java/com/loopeer/codereader/ui/view/ThemeChooser.java +++ /dev/null @@ -1,45 +0,0 @@ -package com.loopeer.codereader.ui.view; - -import android.app.Activity; -import android.content.Context; -import android.view.View; - -import java.util.HashMap; -import java.util.Map; - -public class ThemeChooser { - public interface OnItemSelectListener { - void onItemSelect(int id, String tag); - } - private HashMap mViewThemeTags; - private Context mContext; - private OnItemSelectListener mOnItemSelectListener; - - public ThemeChooser(Context context, OnItemSelectListener onItemSelectListener) { - mContext = context; - mOnItemSelectListener = onItemSelectListener; - mViewThemeTags = new HashMap<>(); - } - - public void addItem(int id, String tag) { - mViewThemeTags.put(id, tag); - } - - public void onItemSelect(View view) { - view.setSelected(true); - mOnItemSelectListener.onItemSelect(view.getId(), mViewThemeTags.get(view.getId())); - for (Integer i : mViewThemeTags.keySet()) { - if (view.getId() != i) { - ((Activity) mContext).findViewById(i).setSelected(false); - } - } - } - - public void onItemSelectByTag(String tag) { - for (Map.Entry entry : mViewThemeTags.entrySet()) { - int id = entry.getKey(); - ((Activity) mContext).findViewById(id).setSelected(entry.getValue().equals(tag)); - } - } - -} diff --git a/app/src/main/java/com/loopeer/codereader/ui/view/ViewDragHelper.java b/app/src/main/java/com/loopeer/codereader/ui/view/ViewDragHelper.java deleted file mode 100644 index 6432fd5..0000000 --- a/app/src/main/java/com/loopeer/codereader/ui/view/ViewDragHelper.java +++ /dev/null @@ -1,1491 +0,0 @@ -package com.loopeer.codereader.ui.view; - -import android.content.Context; -import android.support.v4.view.MotionEventCompat; -import android.support.v4.view.VelocityTrackerCompat; -import android.support.v4.view.ViewCompat; -import android.support.v4.widget.ScrollerCompat; -import android.util.Log; -import android.view.MotionEvent; -import android.view.VelocityTracker; -import android.view.View; -import android.view.ViewConfiguration; -import android.view.ViewGroup; -import android.view.animation.Interpolator; - -import java.util.Arrays; - -public class ViewDragHelper { - private static final String TAG = "ViewDragHelper"; - - /** - * A null/invalid pointer ID. - */ - public static final int INVALID_POINTER = -1; - - /** - * A view is not currently being dragged or animating as a result of a fling/snap. - */ - public static final int STATE_IDLE = 0; - - /** - * A view is currently being dragged. The position is currently changing as a result - * of user input or simulated user input. - */ - public static final int STATE_DRAGGING = 1; - - /** - * A view is currently settling into place as a result of a fling or - * predefined non-interactive motion. - */ - public static final int STATE_SETTLING = 2; - - /** - * Edge flag indicating that the left edge should be affected. - */ - public static final int EDGE_LEFT = 1 << 0; - - /** - * Edge flag indicating that the right edge should be affected. - */ - public static final int EDGE_RIGHT = 1 << 1; - - /** - * Edge flag indicating that the top edge should be affected. - */ - public static final int EDGE_TOP = 1 << 2; - - /** - * Edge flag indicating that the bottom edge should be affected. - */ - public static final int EDGE_BOTTOM = 1 << 3; - - /** - * Edge flag set indicating all edges should be affected. - */ - public static final int EDGE_ALL = EDGE_LEFT | EDGE_TOP | EDGE_RIGHT | EDGE_BOTTOM; - - /** - * Indicates that a check should occur along the horizontal axis - */ - public static final int DIRECTION_HORIZONTAL = 1 << 0; - - /** - * Indicates that a check should occur along the vertical axis - */ - public static final int DIRECTION_VERTICAL = 1 << 1; - - /** - * Indicates that a check should occur along all axes - */ - public static final int DIRECTION_ALL = DIRECTION_HORIZONTAL | DIRECTION_VERTICAL; - - private static final int EDGE_SIZE = 20; // dp - - private static final int BASE_SETTLE_DURATION = 256; // ms - private static final int MAX_SETTLE_DURATION = 600; // ms - - // Current drag state; idle, dragging or settling - private int mDragState; - - // Distance to travel before a drag may begin - private int mTouchSlop; - - // Last known position/pointer tracking - private int mActivePointerId = INVALID_POINTER; - private float[] mInitialMotionX; - private float[] mInitialMotionY; - private float[] mLastMotionX; - private float[] mLastMotionY; - private int[] mInitialEdgesTouched; - private int[] mEdgeDragsInProgress; - private int[] mEdgeDragsLocked; - private int mPointersDown; - - private VelocityTracker mVelocityTracker; - private float mMaxVelocity; - private float mMinVelocity; - - private int mEdgeSize; - private int mTrackingEdges; - - private ScrollerCompat mScroller; - - private final Callback mCallback; - - private View mCapturedView; - private boolean mReleaseInProgress; - - private final ViewGroup mParentView; - - /** - * A Callback is used as a communication channel with the ViewDragHelper back to the - * parent view using it. on*methods are invoked on siginficant events and several - * accessor methods are expected to provide the ViewDragHelper with more information - * about the state of the parent view upon request. The callback also makes decisions - * governing the range and draggability of child views. - */ - public static abstract class Callback { - /** - * Called when the drag state changes. See the STATE_* constants - * for more information. - * - * @param state The new drag state - * - * @see #STATE_IDLE - * @see #STATE_DRAGGING - * @see #STATE_SETTLING - */ - public void onViewDragStateChanged(int state) {} - - /** - * Called when the captured view's position changes as the result of a drag or settle. - * - * @param changedView View whose position changed - * @param left New X coordinate of the left edge of the view - * @param top New Y coordinate of the top edge of the view - * @param dx Change in X position from the last call - * @param dy Change in Y position from the last call - */ - public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {} - - /** - * Called when a child view is captured for dragging or settling. The ID of the pointer - * currently dragging the captured view is supplied. If activePointerId is - * identified as {@link #INVALID_POINTER} the capture is programmatic instead of - * pointer-initiated. - * - * @param capturedChild Child view that was captured - * @param activePointerId Pointer id tracking the child capture - */ - public void onViewCaptured(View capturedChild, int activePointerId) {} - - /** - * Called when the child view is no longer being actively dragged. - * The fling velocity is also supplied, if relevant. The velocity values may - * be clamped to system minimums or maximums. - * - *

Calling code may decide to fling or otherwise release the view to let it - * settle into place. It should do so using {@link #settleCapturedViewAt(int, int)} - * or {@link #flingCapturedView(int, int, int, int)}. If the Callback invokes - * one of these methods, the ViewDragHelper will enter {@link #STATE_SETTLING} - * and the view capture will not fully end until it comes to a complete stop. - * If neither of these methods is invoked before onViewReleased returns, - * the view will stop in place and the ViewDragHelper will return to - * {@link #STATE_IDLE}.

- * - * @param releasedChild The captured child view now being released - * @param xvel X velocity of the pointer as it left the screen in pixels per second. - * @param yvel Y velocity of the pointer as it left the screen in pixels per second. - */ - public void onViewReleased(View releasedChild, float xvel, float yvel) {} - - /** - * Called when one of the subscribed edges in the parent view has been touched - * by the user while no child view is currently captured. - * - * @param edgeFlags A combination of edge flags describing the edge(s) currently touched - * @param pointerId ID of the pointer touching the described edge(s) - * @see #EDGE_LEFT - * @see #EDGE_TOP - * @see #EDGE_RIGHT - * @see #EDGE_BOTTOM - */ - public void onEdgeTouched(int edgeFlags, int pointerId) {} - - /** - * Called when the given edge may become locked. This can happen if an edge drag - * was preliminarily rejected before beginning, but after {@link #onEdgeTouched(int, int)} - * was called. This method should return true to lock this edge or false to leave it - * unlocked. The default behavior is to leave edges unlocked. - * - * @param edgeFlags A combination of edge flags describing the edge(s) locked - * @return true to lock the edge, false to leave it unlocked - */ - public boolean onEdgeLock(int edgeFlags) { - return false; - } - - /** - * Called when the user has started a deliberate drag away from one - * of the subscribed edges in the parent view while no child view is currently captured. - * - * @param edgeFlags A combination of edge flags describing the edge(s) dragged - * @param pointerId ID of the pointer touching the described edge(s) - * @see #EDGE_LEFT - * @see #EDGE_TOP - * @see #EDGE_RIGHT - * @see #EDGE_BOTTOM - */ - public void onEdgeDragStarted(int edgeFlags, int pointerId) {} - - /** - * Called to determine the Z-order of child views. - * - * @param index the ordered position to query for - * @return index of the view that should be ordered at position index - */ - public int getOrderedChildIndex(int index) { - return index; - } - - /** - * Return the magnitude of a draggable child view's horizontal range of motion in pixels. - * This method should return 0 for views that cannot move horizontally. - * - * @param child Child view to check - * @return range of horizontal motion in pixels - */ - public int getViewHorizontalDragRange(View child) { - return 0; - } - - /** - * Return the magnitude of a draggable child view's vertical range of motion in pixels. - * This method should return 0 for views that cannot move vertically. - * - * @param child Child view to check - * @return range of vertical motion in pixels - */ - public int getViewVerticalDragRange(View child) { - return 0; - } - - /** - * Called when the user's input indicates that they want to capture the given child view - * with the pointer indicated by pointerId. The callback should return true if the user - * is permitted to drag the given view with the indicated pointer. - * - *

ViewDragHelper may call this method multiple times for the same view even if - * the view is already captured; this indicates that a new pointer is trying to take - * control of the view.

- * - *

If this method returns true, a call to {@link #onViewCaptured(android.view.View, int)} - * will follow if the capture is successful.

- * - * @param child Child the user is attempting to capture - * @param pointerId ID of the pointer attempting the capture - * @return true if capture should be allowed, false otherwise - */ - public abstract boolean tryCaptureView(View child, int pointerId); - - /** - * Restrict the motion of the dragged child view along the horizontal axis. - * The default implementation does not allow horizontal motion; the extending - * class must override this method and provide the desired clamping. - * - * - * @param child Child view being dragged - * @param left Attempted motion along the X axis - * @param dx Proposed change in position for left - * @return The new clamped position for left - */ - public int clampViewPositionHorizontal(View child, int left, int dx) { - return 0; - } - - /** - * Restrict the motion of the dragged child view along the vertical axis. - * The default implementation does not allow vertical motion; the extending - * class must override this method and provide the desired clamping. - * - * - * @param child Child view being dragged - * @param top Attempted motion along the Y axis - * @param dy Proposed change in position for top - * @return The new clamped position for top - */ - public int clampViewPositionVertical(View child, int top, int dy) { - return 0; - } - } - - /** - * Interpolator defining the animation curve for mScroller - */ - private static final Interpolator sInterpolator = new Interpolator() { - public float getInterpolation(float t) { - t -= 1.0f; - return t * t * t * t * t + 1.0f; - } - }; - - private final Runnable mSetIdleRunnable = new Runnable() { - public void run() { - setDragState(STATE_IDLE); - } - }; - - /** - * Factory method to create a new ViewDragHelper. - * - * @param forParent Parent view to monitor - * @param cb Callback to provide information and receive events - * @return a new ViewDragHelper instance - */ - public static ViewDragHelper create(ViewGroup forParent, Callback cb) { - return new ViewDragHelper(forParent.getContext(), forParent, cb); - } - - /** - * Factory method to create a new ViewDragHelper. - * - * @param forParent Parent view to monitor - * @param sensitivity Multiplier for how sensitive the helper should be about detecting - * the start of a drag. Larger values are more sensitive. 1.0f is normal. - * @param cb Callback to provide information and receive events - * @return a new ViewDragHelper instance - */ - public static ViewDragHelper create(ViewGroup forParent, float sensitivity, Callback cb) { - final ViewDragHelper helper = create(forParent, cb); - helper.mTouchSlop = (int) (helper.mTouchSlop * (1 / sensitivity)); - return helper; - } - - /** - * Apps should use ViewDragHelper.create() to get a new instance. - * This will allow VDH to use internal compatibility implementations for different - * platform versions. - * - * @param context Context to initialize config-dependent params from - * @param forParent Parent view to monitor - */ - private ViewDragHelper(Context context, ViewGroup forParent, Callback cb) { - if (forParent == null) { - throw new IllegalArgumentException("Parent view may not be null"); - } - if (cb == null) { - throw new IllegalArgumentException("Callback may not be null"); - } - - mParentView = forParent; - mCallback = cb; - - final ViewConfiguration vc = ViewConfiguration.get(context); - final float density = context.getResources().getDisplayMetrics().density; - mEdgeSize = (int) (EDGE_SIZE * density + 0.5f); - - mTouchSlop = vc.getScaledTouchSlop(); - mMaxVelocity = vc.getScaledMaximumFlingVelocity(); - mMinVelocity = vc.getScaledMinimumFlingVelocity(); - mScroller = ScrollerCompat.create(context, sInterpolator); - } - - /** - * Set the minimum velocity that will be detected as having a magnitude greater than zero - * in pixels per second. Callback methods accepting a velocity will be clamped appropriately. - * - * @param minVel Minimum velocity to detect - */ - public void setMinVelocity(float minVel) { - mMinVelocity = minVel; - } - - /** - * Return the currently configured minimum velocity. Any flings with a magnitude less - * than this value in pixels per second. Callback methods accepting a velocity will receive - * zero as a velocity value if the real detected velocity was below this threshold. - * - * @return the minimum velocity that will be detected - */ - public float getMinVelocity() { - return mMinVelocity; - } - - /** - * Retrieve the current drag state of this helper. This will return one of - * {@link #STATE_IDLE}, {@link #STATE_DRAGGING} or {@link #STATE_SETTLING}. - * @return The current drag state - */ - public int getViewDragState() { - return mDragState; - } - - /** - * Enable edge tracking for the selected edges of the parent view. - * The callback's {@link Callback#onEdgeTouched(int, int)} and - * {@link Callback#onEdgeDragStarted(int, int)} methods will only be invoked - * for edges for which edge tracking has been enabled. - * - * @param edgeFlags Combination of edge flags describing the edges to watch - * @see #EDGE_LEFT - * @see #EDGE_TOP - * @see #EDGE_RIGHT - * @see #EDGE_BOTTOM - */ - public void setEdgeTrackingEnabled(int edgeFlags) { - mTrackingEdges = edgeFlags; - } - - /** - * Return the size of an edge. This is the range in pixels along the edges of this view - * that will actively detect edge touches or drags if edge tracking is enabled. - * - * @return The size of an edge in pixels - * @see #setEdgeTrackingEnabled(int) - */ - public int getEdgeSize() { - return mEdgeSize; - } - - /** - * Capture a specific child view for dragging within the parent. The callback will be notified - * but {@link Callback#tryCaptureView(android.view.View, int)} will not be asked permission to - * capture this view. - * - * @param childView Child view to capture - * @param activePointerId ID of the pointer that is dragging the captured child view - */ - public void captureChildView(View childView, int activePointerId) { - if (childView.getParent() != mParentView) { - throw new IllegalArgumentException("captureChildView: parameter must be a descendant " + - "of the ViewDragHelper's tracked parent view (" + mParentView + ")"); - } - - mCapturedView = childView; - mActivePointerId = activePointerId; - mCallback.onViewCaptured(childView, activePointerId); - setDragState(STATE_DRAGGING); - } - - /** - * @return The currently captured view, or null if no view has been captured. - */ - public View getCapturedView() { - return mCapturedView; - } - - /** - * @return The ID of the pointer currently dragging the captured view, - * or {@link #INVALID_POINTER}. - */ - public int getActivePointerId() { - return mActivePointerId; - } - - /** - * @return The minimum distance in pixels that the user must travel to initiate a drag - */ - public int getTouchSlop() { - return mTouchSlop; - } - - /** - * The result of a call to this method is equivalent to - * {@link #processTouchEvent(android.view.MotionEvent)} receiving an ACTION_CANCEL event. - */ - public void cancel() { - mActivePointerId = INVALID_POINTER; - clearMotionHistory(); - - if (mVelocityTracker != null) { - mVelocityTracker.recycle(); - mVelocityTracker = null; - } - } - - /** - * {@link #cancel()}, but also abort all motion in progress and snap to the end of any - * animation. - */ - public void abort() { - cancel(); - if (mDragState == STATE_SETTLING) { - final int oldX = mScroller.getCurrX(); - final int oldY = mScroller.getCurrY(); - mScroller.abortAnimation(); - final int newX = mScroller.getCurrX(); - final int newY = mScroller.getCurrY(); - mCallback.onViewPositionChanged(mCapturedView, newX, newY, newX - oldX, newY - oldY); - } - setDragState(STATE_IDLE); - } - - /** - * Animate the view child to the given (left, top) position. - * If this method returns true, the caller should invoke {@link #continueSettling(boolean)} - * on each subsequent frame to continue the motion until it returns false. If this method - * returns false there is no further work to do to complete the movement. - * - *

This operation does not count as a capture event, though {@link #getCapturedView()} - * will still report the sliding view while the slide is in progress.

- * - * @param child Child view to capture and animate - * @param finalLeft Final left position of child - * @param finalTop Final top position of child - * @return true if animation should continue through {@link #continueSettling(boolean)} calls - */ - public boolean smoothSlideViewTo(View child, int finalLeft, int finalTop) { - mCapturedView = child; - mActivePointerId = INVALID_POINTER; - - boolean continueSliding = forceSettleCapturedViewAt(finalLeft, finalTop, 0, 0); - if (!continueSliding && mDragState == STATE_IDLE && mCapturedView != null) { - // If we're in an IDLE state to begin with and aren't moving anywhere, we - // end up having a non-null capturedView with an IDLE dragState - mCapturedView = null; - } - - return continueSliding; - } - - /** - * Settle the captured view at the given (left, top) position. - * The appropriate velocity from prior motion will be taken into account. - * If this method returns true, the caller should invoke {@link #continueSettling(boolean)} - * on each subsequent frame to continue the motion until it returns false. If this method - * returns false there is no further work to do to complete the movement. - * - * @param finalLeft Settled left edge position for the captured view - * @param finalTop Settled top edge position for the captured view - * @return true if animation should continue through {@link #continueSettling(boolean)} calls - */ - public boolean settleCapturedViewAt(int finalLeft, int finalTop) { - if (!mReleaseInProgress) { - throw new IllegalStateException("Cannot settleCapturedViewAt outside of a call to " + - "Callback#onViewReleased"); - } - - return forceSettleCapturedViewAt(finalLeft, finalTop, - (int) VelocityTrackerCompat.getXVelocity(mVelocityTracker, mActivePointerId), - (int) VelocityTrackerCompat.getYVelocity(mVelocityTracker, mActivePointerId)); - } - - /** - * Settle the captured view at the given (left, top) position. - * - * @param finalLeft Target left position for the captured view - * @param finalTop Target top position for the captured view - * @param xvel Horizontal velocity - * @param yvel Vertical velocity - * @return true if animation should continue through {@link #continueSettling(boolean)} calls - */ - private boolean forceSettleCapturedViewAt(int finalLeft, int finalTop, int xvel, int yvel) { - final int startLeft = mCapturedView.getLeft(); - final int startTop = mCapturedView.getTop(); - final int dx = finalLeft - startLeft; - final int dy = finalTop - startTop; - - if (dx == 0 && dy == 0) { - // Nothing to do. Send callbacks, be done. - mScroller.abortAnimation(); - setDragState(STATE_IDLE); - return false; - } - - final int duration = computeSettleDuration(mCapturedView, dx, dy, xvel, yvel); - mScroller.startScroll(startLeft, startTop, dx, dy, duration); - - setDragState(STATE_SETTLING); - return true; - } - - private int computeSettleDuration(View child, int dx, int dy, int xvel, int yvel) { - xvel = clampMag(xvel, (int) mMinVelocity, (int) mMaxVelocity); - yvel = clampMag(yvel, (int) mMinVelocity, (int) mMaxVelocity); - final int absDx = Math.abs(dx); - final int absDy = Math.abs(dy); - final int absXVel = Math.abs(xvel); - final int absYVel = Math.abs(yvel); - final int addedVel = absXVel + absYVel; - final int addedDistance = absDx + absDy; - - final float xweight = xvel != 0 ? (float) absXVel / addedVel : - (float) absDx / addedDistance; - final float yweight = yvel != 0 ? (float) absYVel / addedVel : - (float) absDy / addedDistance; - - int xduration = computeAxisDuration(dx, xvel, mCallback.getViewHorizontalDragRange(child)); - int yduration = computeAxisDuration(dy, yvel, mCallback.getViewVerticalDragRange(child)); - - return (int) (xduration * xweight + yduration * yweight); - } - - private int computeAxisDuration(int delta, int velocity, int motionRange) { - if (delta == 0) { - return 0; - } - - final int width = mParentView.getWidth(); - final int halfWidth = width / 2; - final float distanceRatio = Math.min(1f, (float) Math.abs(delta) / width); - final float distance = halfWidth + halfWidth * - distanceInfluenceForSnapDuration(distanceRatio); - - int duration; - velocity = Math.abs(velocity); - if (velocity > 0) { - duration = 4 * Math.round(1000 * Math.abs(distance / velocity)); - } else { - final float range = (float) Math.abs(delta) / motionRange; - duration = (int) ((range + 1) * BASE_SETTLE_DURATION); - } - return Math.min(duration, MAX_SETTLE_DURATION); - } - - /** - * Clamp the magnitude of value for absMin and absMax. - * If the value is below the minimum, it will be clamped to zero. - * If the value is above the maximum, it will be clamped to the maximum. - * - * @param value Value to clamp - * @param absMin Absolute value of the minimum significant value to return - * @param absMax Absolute value of the maximum value to return - * @return The clamped value with the same sign as value - */ - private int clampMag(int value, int absMin, int absMax) { - final int absValue = Math.abs(value); - if (absValue < absMin) return 0; - if (absValue > absMax) return value > 0 ? absMax : -absMax; - return value; - } - - /** - * Clamp the magnitude of value for absMin and absMax. - * If the value is below the minimum, it will be clamped to zero. - * If the value is above the maximum, it will be clamped to the maximum. - * - * @param value Value to clamp - * @param absMin Absolute value of the minimum significant value to return - * @param absMax Absolute value of the maximum value to return - * @return The clamped value with the same sign as value - */ - private float clampMag(float value, float absMin, float absMax) { - final float absValue = Math.abs(value); - if (absValue < absMin) return 0; - if (absValue > absMax) return value > 0 ? absMax : -absMax; - return value; - } - - private float distanceInfluenceForSnapDuration(float f) { - f -= 0.5f; // center the values about 0. - f *= 0.3f * Math.PI / 2.0f; - return (float) Math.sin(f); - } - - /** - * Settle the captured view based on standard free-moving fling behavior. - * The caller should invoke {@link #continueSettling(boolean)} on each subsequent frame - * to continue the motion until it returns false. - * - * @param minLeft Minimum X position for the view's left edge - * @param minTop Minimum Y position for the view's top edge - * @param maxLeft Maximum X position for the view's left edge - * @param maxTop Maximum Y position for the view's top edge - */ - public void flingCapturedView(int minLeft, int minTop, int maxLeft, int maxTop) { - if (!mReleaseInProgress) { - throw new IllegalStateException("Cannot flingCapturedView outside of a call to " + - "Callback#onViewReleased"); - } - - mScroller.fling(mCapturedView.getLeft(), mCapturedView.getTop(), - (int) VelocityTrackerCompat.getXVelocity(mVelocityTracker, mActivePointerId), - (int) VelocityTrackerCompat.getYVelocity(mVelocityTracker, mActivePointerId), - minLeft, maxLeft, minTop, maxTop); - - setDragState(STATE_SETTLING); - } - - /** - * Move the captured settling view by the appropriate amount for the current time. - * If continueSettling returns true, the caller should call it again - * on the next frame to continue. - * - * @param deferCallbacks true if state callbacks should be deferred via posted message. - * Set this to true if you are calling this method from - * {@link android.view.View#computeScroll()} or similar methods - * invoked as part of layout or drawing. - * @return true if settle is still in progress - */ - public boolean continueSettling(boolean deferCallbacks) { - if (mDragState == STATE_SETTLING) { - boolean keepGoing = mScroller.computeScrollOffset(); - final int x = mScroller.getCurrX(); - final int y = mScroller.getCurrY(); - final int dx = x - mCapturedView.getLeft(); - final int dy = y - mCapturedView.getTop(); - - if (dx != 0) { - ViewCompat.offsetLeftAndRight(mCapturedView, dx); - } - if (dy != 0) { - ViewCompat.offsetTopAndBottom(mCapturedView, dy); - } - - if (dx != 0 || dy != 0) { - mCallback.onViewPositionChanged(mCapturedView, x, y, dx, dy); - } - - if (keepGoing && x == mScroller.getFinalX() && y == mScroller.getFinalY()) { - // Close enough. The interpolator/scroller might think we're still moving - // but the user sure doesn't. - mScroller.abortAnimation(); - keepGoing = false; - } - - if (!keepGoing) { - if (deferCallbacks) { - mParentView.post(mSetIdleRunnable); - } else { - setDragState(STATE_IDLE); - } - } - } - - return mDragState == STATE_SETTLING; - } - - /** - * Like all callback events this must happen on the UI thread, but release - * involves some extra semantics. During a release (mReleaseInProgress) - * is the only time it is valid to call {@link #settleCapturedViewAt(int, int)} - * or {@link #flingCapturedView(int, int, int, int)}. - */ - private void dispatchViewReleased(float xvel, float yvel) { - mReleaseInProgress = true; - mCallback.onViewReleased(mCapturedView, xvel, yvel); - mReleaseInProgress = false; - - if (mDragState == STATE_DRAGGING) { - // onViewReleased didn't call a method that would have changed this. Go idle. - setDragState(STATE_IDLE); - } - } - - private void clearMotionHistory() { - if (mInitialMotionX == null) { - return; - } - Arrays.fill(mInitialMotionX, 0); - Arrays.fill(mInitialMotionY, 0); - Arrays.fill(mLastMotionX, 0); - Arrays.fill(mLastMotionY, 0); - Arrays.fill(mInitialEdgesTouched, 0); - Arrays.fill(mEdgeDragsInProgress, 0); - Arrays.fill(mEdgeDragsLocked, 0); - mPointersDown = 0; - } - - private void clearMotionHistory(int pointerId) { - if (mInitialMotionX == null || !isPointerDown(pointerId)) { - return; - } - mInitialMotionX[pointerId] = 0; - mInitialMotionY[pointerId] = 0; - mLastMotionX[pointerId] = 0; - mLastMotionY[pointerId] = 0; - mInitialEdgesTouched[pointerId] = 0; - mEdgeDragsInProgress[pointerId] = 0; - mEdgeDragsLocked[pointerId] = 0; - mPointersDown &= ~(1 << pointerId); - } - - private void ensureMotionHistorySizeForId(int pointerId) { - if (mInitialMotionX == null || mInitialMotionX.length <= pointerId) { - float[] imx = new float[pointerId + 1]; - float[] imy = new float[pointerId + 1]; - float[] lmx = new float[pointerId + 1]; - float[] lmy = new float[pointerId + 1]; - int[] iit = new int[pointerId + 1]; - int[] edip = new int[pointerId + 1]; - int[] edl = new int[pointerId + 1]; - - if (mInitialMotionX != null) { - System.arraycopy(mInitialMotionX, 0, imx, 0, mInitialMotionX.length); - System.arraycopy(mInitialMotionY, 0, imy, 0, mInitialMotionY.length); - System.arraycopy(mLastMotionX, 0, lmx, 0, mLastMotionX.length); - System.arraycopy(mLastMotionY, 0, lmy, 0, mLastMotionY.length); - System.arraycopy(mInitialEdgesTouched, 0, iit, 0, mInitialEdgesTouched.length); - System.arraycopy(mEdgeDragsInProgress, 0, edip, 0, mEdgeDragsInProgress.length); - System.arraycopy(mEdgeDragsLocked, 0, edl, 0, mEdgeDragsLocked.length); - } - - mInitialMotionX = imx; - mInitialMotionY = imy; - mLastMotionX = lmx; - mLastMotionY = lmy; - mInitialEdgesTouched = iit; - mEdgeDragsInProgress = edip; - mEdgeDragsLocked = edl; - } - } - - private void saveInitialMotion(float x, float y, int pointerId) { - ensureMotionHistorySizeForId(pointerId); - mInitialMotionX[pointerId] = mLastMotionX[pointerId] = x; - mInitialMotionY[pointerId] = mLastMotionY[pointerId] = y; - mInitialEdgesTouched[pointerId] = getEdgesTouched((int) x, (int) y); - mPointersDown |= 1 << pointerId; - } - - private void saveLastMotion(MotionEvent ev) { - final int pointerCount = MotionEventCompat.getPointerCount(ev); - for (int i = 0; i < pointerCount; i++) { - final int pointerId = MotionEventCompat.getPointerId(ev, i); - // If pointer is invalid then skip saving on ACTION_MOVE. - if (!isValidPointerForActionMove(pointerId)) { - continue; - } - final float x = MotionEventCompat.getX(ev, i); - final float y = MotionEventCompat.getY(ev, i); - mLastMotionX[pointerId] = x; - mLastMotionY[pointerId] = y; - } - } - - /** - * Check if the given pointer ID represents a pointer that is currently down (to the best - * of the ViewDragHelper's knowledge). - * - *

The state used to report this information is populated by the methods - * {@link #shouldInterceptTouchEvent(android.view.MotionEvent)} or - * {@link #processTouchEvent(android.view.MotionEvent)}. If one of these methods has not - * been called for all relevant MotionEvents to track, the information reported - * by this method may be stale or incorrect.

- * - * @param pointerId pointer ID to check; corresponds to IDs provided by MotionEvent - * @return true if the pointer with the given ID is still down - */ - public boolean isPointerDown(int pointerId) { - return (mPointersDown & 1 << pointerId) != 0; - } - - void setDragState(int state) { - mParentView.removeCallbacks(mSetIdleRunnable); - if (mDragState != state) { - mDragState = state; - mCallback.onViewDragStateChanged(state); - if (mDragState == STATE_IDLE) { - mCapturedView = null; - } - } - } - - /** - * Attempt to capture the view with the given pointer ID. The callback will be involved. - * This will put us into the "dragging" state. If we've already captured this view with - * this pointer this method will immediately return true without consulting the callback. - * - * @param toCapture View to capture - * @param pointerId Pointer to capture with - * @return true if capture was successful - */ - boolean tryCaptureViewForDrag(View toCapture, int pointerId) { - if (toCapture == mCapturedView && mActivePointerId == pointerId) { - // Already done! - return true; - } - if (toCapture != null && mCallback.tryCaptureView(toCapture, pointerId)) { - mActivePointerId = pointerId; - captureChildView(toCapture, pointerId); - return true; - } - return false; - } - - /** - * Tests scrollability within child views of v given a delta of dx. - * - * @param v View to test for horizontal scrollability - * @param checkV Whether the view v passed should itself be checked for scrollability (true), - * or just its children (false). - * @param dx Delta scrolled in pixels along the X axis - * @param dy Delta scrolled in pixels along the Y axis - * @param x X coordinate of the active touch point - * @param y Y coordinate of the active touch point - * @return true if child views of v can be scrolled by delta of dx. - */ - protected boolean canScroll(View v, boolean checkV, int dx, int dy, int x, int y) { - if (v instanceof ViewGroup) { - final ViewGroup group = (ViewGroup) v; - final int scrollX = v.getScrollX(); - final int scrollY = v.getScrollY(); - final int count = group.getChildCount(); - // Count backwards - let topmost views consume scroll distance first. - for (int i = count - 1; i >= 0; i--) { - // TODO: Add versioned support here for transformed views. - // This will not work for transformed views in Honeycomb+ - final View child = group.getChildAt(i); - if (x + scrollX >= child.getLeft() && x + scrollX < child.getRight() && - y + scrollY >= child.getTop() && y + scrollY < child.getBottom() && - canScroll(child, true, dx, dy, x + scrollX - child.getLeft(), - y + scrollY - child.getTop())) { - return true; - } - } - } - - return checkV && (ViewCompat.canScrollHorizontally(v, -dx) || - ViewCompat.canScrollVertically(v, -dy)); - } - - /** - * Check if this event as provided to the parent view's onInterceptTouchEvent should - * cause the parent to intercept the touch event stream. - * - * @param ev MotionEvent provided to onInterceptTouchEvent - * @return true if the parent view should return true from onInterceptTouchEvent - */ - public boolean shouldInterceptTouchEvent(MotionEvent ev) { - final int action = MotionEventCompat.getActionMasked(ev); - final int actionIndex = MotionEventCompat.getActionIndex(ev); - - if (action == MotionEvent.ACTION_DOWN) { - // Reset things for a new event stream, just in case we didn't get - // the whole previous stream. - cancel(); - } - - if (mVelocityTracker == null) { - mVelocityTracker = VelocityTracker.obtain(); - } - mVelocityTracker.addMovement(ev); - - switch (action) { - case MotionEvent.ACTION_DOWN: { - final float x = ev.getX(); - final float y = ev.getY(); - final int pointerId = MotionEventCompat.getPointerId(ev, 0); - saveInitialMotion(x, y, pointerId); - - final View toCapture = findTopChildUnder((int) x, (int) y); - - // Catch a settling view if possible. - if (toCapture == mCapturedView && mDragState == STATE_SETTLING) { - tryCaptureViewForDrag(toCapture, pointerId); - } - - final int edgesTouched = mInitialEdgesTouched[pointerId]; - if ((edgesTouched & mTrackingEdges) != 0) { - mCallback.onEdgeTouched(edgesTouched & mTrackingEdges, pointerId); - } - break; - } - - case MotionEventCompat.ACTION_POINTER_DOWN: { - final int pointerId = MotionEventCompat.getPointerId(ev, actionIndex); - final float x = MotionEventCompat.getX(ev, actionIndex); - final float y = MotionEventCompat.getY(ev, actionIndex); - - saveInitialMotion(x, y, pointerId); - - // A ViewDragHelper can only manipulate one view at a time. - if (mDragState == STATE_IDLE) { - final int edgesTouched = mInitialEdgesTouched[pointerId]; - if ((edgesTouched & mTrackingEdges) != 0) { - mCallback.onEdgeTouched(edgesTouched & mTrackingEdges, pointerId); - } - } else if (mDragState == STATE_SETTLING) { - // Catch a settling view if possible. - final View toCapture = findTopChildUnder((int) x, (int) y); - if (toCapture == mCapturedView) { - tryCaptureViewForDrag(toCapture, pointerId); - } - } - break; - } - - case MotionEvent.ACTION_MOVE: { - if (mInitialMotionX == null || mInitialMotionY == null) break; - - // First to cross a touch slop over a draggable view wins. Also report edge drags. - final int pointerCount = MotionEventCompat.getPointerCount(ev); - for (int i = 0; i < pointerCount; i++) { - final int pointerId = MotionEventCompat.getPointerId(ev, i); - - // If pointer is invalid then skip the ACTION_MOVE. - if (!isValidPointerForActionMove(pointerId)) continue; - - final float x = MotionEventCompat.getX(ev, i); - final float y = MotionEventCompat.getY(ev, i); - final float dx = x - mInitialMotionX[pointerId]; - final float dy = y - mInitialMotionY[pointerId]; - - final View toCapture = findTopChildUnder((int) x, (int) y); - final boolean pastSlop = toCapture != null && checkTouchSlop(toCapture, dx, dy); - if (pastSlop) { - // check the callback's - // getView[Horizontal|Vertical]DragRange methods to know - // if you can move at all along an axis, then see if it - // would clamp to the same value. If you can't move at - // all in every dimension with a nonzero range, bail. - final int oldLeft = toCapture.getLeft(); - final int targetLeft = oldLeft + (int) dx; - final int newLeft = mCallback.clampViewPositionHorizontal(toCapture, - targetLeft, (int) dx); - final int oldTop = toCapture.getTop(); - final int targetTop = oldTop + (int) dy; - final int newTop = mCallback.clampViewPositionVertical(toCapture, targetTop, - (int) dy); - final int horizontalDragRange = mCallback.getViewHorizontalDragRange( - toCapture); - final int verticalDragRange = mCallback.getViewVerticalDragRange(toCapture); - if ((horizontalDragRange == 0 || horizontalDragRange > 0 - && newLeft == oldLeft) && (verticalDragRange == 0 - || verticalDragRange > 0 && newTop == oldTop)) { - break; - } - } - reportNewEdgeDrags(dx, dy, pointerId); - if (mDragState == STATE_DRAGGING) { - // Callback might have started an edge drag - break; - } - - if (pastSlop && tryCaptureViewForDrag(toCapture, pointerId)) { - break; - } - } - saveLastMotion(ev); - break; - } - - case MotionEventCompat.ACTION_POINTER_UP: { - final int pointerId = MotionEventCompat.getPointerId(ev, actionIndex); - clearMotionHistory(pointerId); - break; - } - - case MotionEvent.ACTION_UP: - case MotionEvent.ACTION_CANCEL: { - cancel(); - break; - } - } - - return mDragState == STATE_DRAGGING; - } - - /** - * Process a touch event received by the parent view. This method will dispatch callback events - * as needed before returning. The parent view's onTouchEvent implementation should call this. - * - * @param ev The touch event received by the parent view - */ - public void processTouchEvent(MotionEvent ev) { - final int action = MotionEventCompat.getActionMasked(ev); - final int actionIndex = MotionEventCompat.getActionIndex(ev); - - if (action == MotionEvent.ACTION_DOWN) { - // Reset things for a new event stream, just in case we didn't get - // the whole previous stream. - cancel(); - } - - if (mVelocityTracker == null) { - mVelocityTracker = VelocityTracker.obtain(); - } - mVelocityTracker.addMovement(ev); - - switch (action) { - case MotionEvent.ACTION_DOWN: { - final float x = ev.getX(); - final float y = ev.getY(); - final int pointerId = MotionEventCompat.getPointerId(ev, 0); - final View toCapture = findTopChildUnder((int) x, (int) y); - - saveInitialMotion(x, y, pointerId); - - // Since the parent is already directly processing this touch event, - // there is no reason to delay for a slop before dragging. - // Start immediately if possible. - tryCaptureViewForDrag(toCapture, pointerId); - - final int edgesTouched = mInitialEdgesTouched[pointerId]; - if ((edgesTouched & mTrackingEdges) != 0) { - mCallback.onEdgeTouched(edgesTouched & mTrackingEdges, pointerId); - } - break; - } - - case MotionEventCompat.ACTION_POINTER_DOWN: { - final int pointerId = MotionEventCompat.getPointerId(ev, actionIndex); - final float x = MotionEventCompat.getX(ev, actionIndex); - final float y = MotionEventCompat.getY(ev, actionIndex); - - saveInitialMotion(x, y, pointerId); - - // A ViewDragHelper can only manipulate one view at a time. - if (mDragState == STATE_IDLE) { - // If we're idle we can do anything! Treat it like a normal down event. - - final View toCapture = findTopChildUnder((int) x, (int) y); - tryCaptureViewForDrag(toCapture, pointerId); - - final int edgesTouched = mInitialEdgesTouched[pointerId]; - if ((edgesTouched & mTrackingEdges) != 0) { - mCallback.onEdgeTouched(edgesTouched & mTrackingEdges, pointerId); - } - } else if (isCapturedViewUnder((int) x, (int) y)) { - // We're still tracking a captured view. If the same view is under this - // point, we'll swap to controlling it with this pointer instead. - // (This will still work if we're "catching" a settling view.) - - tryCaptureViewForDrag(mCapturedView, pointerId); - } - break; - } - - case MotionEvent.ACTION_MOVE: { - if (mDragState == STATE_DRAGGING) { - // If pointer is invalid then skip the ACTION_MOVE. - if (!isValidPointerForActionMove(mActivePointerId)) break; - - final int index = MotionEventCompat.findPointerIndex(ev, mActivePointerId); - final float x = MotionEventCompat.getX(ev, index); - final float y = MotionEventCompat.getY(ev, index); - final int idx = (int) (x - mLastMotionX[mActivePointerId]); - final int idy = (int) (y - mLastMotionY[mActivePointerId]); - - dragTo(mCapturedView.getLeft() + idx, mCapturedView.getTop() + idy, idx, idy); - - saveLastMotion(ev); - } else { - // Check to see if any pointer is now over a draggable view. - final int pointerCount = MotionEventCompat.getPointerCount(ev); - for (int i = 0; i < pointerCount; i++) { - final int pointerId = MotionEventCompat.getPointerId(ev, i); - - // If pointer is invalid then skip the ACTION_MOVE. - if (!isValidPointerForActionMove(pointerId)) continue; - - final float x = MotionEventCompat.getX(ev, i); - final float y = MotionEventCompat.getY(ev, i); - final float dx = x - mInitialMotionX[pointerId]; - final float dy = y - mInitialMotionY[pointerId]; - - reportNewEdgeDrags(dx, dy, pointerId); - if (mDragState == STATE_DRAGGING) { - // Callback might have started an edge drag. - break; - } - - final View toCapture = findTopChildUnder((int) x, (int) y); - if (checkTouchSlop(toCapture, dx, dy) && - tryCaptureViewForDrag(toCapture, pointerId)) { - break; - } - } - saveLastMotion(ev); - } - break; - } - - case MotionEventCompat.ACTION_POINTER_UP: { - final int pointerId = MotionEventCompat.getPointerId(ev, actionIndex); - if (mDragState == STATE_DRAGGING && pointerId == mActivePointerId) { - // Try to find another pointer that's still holding on to the captured view. - int newActivePointer = INVALID_POINTER; - final int pointerCount = MotionEventCompat.getPointerCount(ev); - for (int i = 0; i < pointerCount; i++) { - final int id = MotionEventCompat.getPointerId(ev, i); - if (id == mActivePointerId) { - // This one's going away, skip. - continue; - } - - final float x = MotionEventCompat.getX(ev, i); - final float y = MotionEventCompat.getY(ev, i); - if (findTopChildUnder((int) x, (int) y) == mCapturedView && - tryCaptureViewForDrag(mCapturedView, id)) { - newActivePointer = mActivePointerId; - break; - } - } - - if (newActivePointer == INVALID_POINTER) { - // We didn't find another pointer still touching the view, release it. - releaseViewForPointerUp(); - } - } - clearMotionHistory(pointerId); - break; - } - - case MotionEvent.ACTION_UP: { - if (mDragState == STATE_DRAGGING) { - releaseViewForPointerUp(); - } - cancel(); - break; - } - - case MotionEvent.ACTION_CANCEL: { - if (mDragState == STATE_DRAGGING) { - dispatchViewReleased(0, 0); - } - cancel(); - break; - } - } - } - - private void reportNewEdgeDrags(float dx, float dy, int pointerId) { - int dragsStarted = 0; - if (checkNewEdgeDrag(dx, dy, pointerId, EDGE_LEFT)) { - dragsStarted |= EDGE_LEFT; - } - if (checkNewEdgeDrag(dy, dx, pointerId, EDGE_TOP)) { - dragsStarted |= EDGE_TOP; - } - if (checkNewEdgeDrag(dx, dy, pointerId, EDGE_RIGHT)) { - dragsStarted |= EDGE_RIGHT; - } - if (checkNewEdgeDrag(dy, dx, pointerId, EDGE_BOTTOM)) { - dragsStarted |= EDGE_BOTTOM; - } - - if (dragsStarted != 0) { - mEdgeDragsInProgress[pointerId] |= dragsStarted; - mCallback.onEdgeDragStarted(dragsStarted, pointerId); - } - } - - private boolean checkNewEdgeDrag(float delta, float odelta, int pointerId, int edge) { - final float absDelta = Math.abs(delta); - final float absODelta = Math.abs(odelta); - - if ((mInitialEdgesTouched[pointerId] & edge) != edge || (mTrackingEdges & edge) == 0 || - (mEdgeDragsLocked[pointerId] & edge) == edge || - (mEdgeDragsInProgress[pointerId] & edge) == edge || - (absDelta <= mTouchSlop && absODelta <= mTouchSlop)) { - return false; - } - if (absDelta < absODelta * 0.5f && mCallback.onEdgeLock(edge)) { - mEdgeDragsLocked[pointerId] |= edge; - return false; - } - return (mEdgeDragsInProgress[pointerId] & edge) == 0 && absDelta > mTouchSlop; - } - - /** - * Check if we've crossed a reasonable touch slop for the given child view. - * If the child cannot be dragged along the horizontal or vertical axis, motion - * along that axis will not count toward the slop check. - * - * @param child Child to check - * @param dx Motion since initial position along X axis - * @param dy Motion since initial position along Y axis - * @return true if the touch slop has been crossed - */ - private boolean checkTouchSlop(View child, float dx, float dy) { - if (child == null) { - return false; - } - final boolean checkHorizontal = mCallback.getViewHorizontalDragRange(child) > 0; - final boolean checkVertical = mCallback.getViewVerticalDragRange(child) > 0; - if ((dx >= 0 && ViewCompat.canScrollHorizontally(child, -1)) - || (dx <= 0 && ViewCompat.canScrollHorizontally(child, 1))) { - - } else { - if (checkHorizontal && checkVertical) { - return dx * dx + dy * dy > mTouchSlop * mTouchSlop; - } else if (checkHorizontal) { - return Math.abs(dx) > mTouchSlop; - } else if (checkVertical) { - return Math.abs(dy) > mTouchSlop; - } - } - return false; - } - - /** - * Check if any pointer tracked in the current gesture has crossed - * the required slop threshold. - * - *

This depends on internal state populated by - * {@link #shouldInterceptTouchEvent(android.view.MotionEvent)} or - * {@link #processTouchEvent(android.view.MotionEvent)}. You should only rely on - * the results of this method after all currently available touch data - * has been provided to one of these two methods.

- * - * @param directions Combination of direction flags, see {@link #DIRECTION_HORIZONTAL}, - * {@link #DIRECTION_VERTICAL}, {@link #DIRECTION_ALL} - * @return true if the slop threshold has been crossed, false otherwise - */ - public boolean checkTouchSlop(int directions) { - final int count = mInitialMotionX.length; - for (int i = 0; i < count; i++) { - if (checkTouchSlop(directions, i)) { - return true; - } - } - return false; - } - - /** - * Check if the specified pointer tracked in the current gesture has crossed - * the required slop threshold. - * - *

This depends on internal state populated by - * {@link #shouldInterceptTouchEvent(android.view.MotionEvent)} or - * {@link #processTouchEvent(android.view.MotionEvent)}. You should only rely on - * the results of this method after all currently available touch data - * has been provided to one of these two methods.

- * - * @param directions Combination of direction flags, see {@link #DIRECTION_HORIZONTAL}, - * {@link #DIRECTION_VERTICAL}, {@link #DIRECTION_ALL} - * @param pointerId ID of the pointer to slop check as specified by MotionEvent - * @return true if the slop threshold has been crossed, false otherwise - */ - public boolean checkTouchSlop(int directions, int pointerId) { - if (!isPointerDown(pointerId)) { - return false; - } - - final boolean checkHorizontal = (directions & DIRECTION_HORIZONTAL) == DIRECTION_HORIZONTAL; - final boolean checkVertical = (directions & DIRECTION_VERTICAL) == DIRECTION_VERTICAL; - - final float dx = mLastMotionX[pointerId] - mInitialMotionX[pointerId]; - final float dy = mLastMotionY[pointerId] - mInitialMotionY[pointerId]; - - if (checkHorizontal && checkVertical) { - return dx * dx + dy * dy > mTouchSlop * mTouchSlop; - } else if (checkHorizontal) { - return Math.abs(dx) > mTouchSlop; - } else if (checkVertical) { - return Math.abs(dy) > mTouchSlop; - } - return false; - } - - /** - * Check if any of the edges specified were initially touched in the currently active gesture. - * If there is no currently active gesture this method will return false. - * - * @param edges Edges to check for an initial edge touch. See {@link #EDGE_LEFT}, - * {@link #EDGE_TOP}, {@link #EDGE_RIGHT}, {@link #EDGE_BOTTOM} and - * {@link #EDGE_ALL} - * @return true if any of the edges specified were initially touched in the current gesture - */ - public boolean isEdgeTouched(int edges) { - final int count = mInitialEdgesTouched.length; - for (int i = 0; i < count; i++) { - if (isEdgeTouched(edges, i)) { - return true; - } - } - return false; - } - - /** - * Check if any of the edges specified were initially touched by the pointer with - * the specified ID. If there is no currently active gesture or if there is no pointer with - * the given ID currently down this method will return false. - * - * @param edges Edges to check for an initial edge touch. See {@link #EDGE_LEFT}, - * {@link #EDGE_TOP}, {@link #EDGE_RIGHT}, {@link #EDGE_BOTTOM} and - * {@link #EDGE_ALL} - * @return true if any of the edges specified were initially touched in the current gesture - */ - public boolean isEdgeTouched(int edges, int pointerId) { - return isPointerDown(pointerId) && (mInitialEdgesTouched[pointerId] & edges) != 0; - } - - private void releaseViewForPointerUp() { - mVelocityTracker.computeCurrentVelocity(1000, mMaxVelocity); - final float xvel = clampMag( - VelocityTrackerCompat.getXVelocity(mVelocityTracker, mActivePointerId), - mMinVelocity, mMaxVelocity); - final float yvel = clampMag( - VelocityTrackerCompat.getYVelocity(mVelocityTracker, mActivePointerId), - mMinVelocity, mMaxVelocity); - dispatchViewReleased(xvel, yvel); - } - - private void dragTo(int left, int top, int dx, int dy) { - int clampedX = left; - int clampedY = top; - final int oldLeft = mCapturedView.getLeft(); - final int oldTop = mCapturedView.getTop(); - if (dx != 0) { - clampedX = mCallback.clampViewPositionHorizontal(mCapturedView, left, dx); - ViewCompat.offsetLeftAndRight(mCapturedView, clampedX - oldLeft); - } - if (dy != 0) { - clampedY = mCallback.clampViewPositionVertical(mCapturedView, top, dy); - ViewCompat.offsetTopAndBottom(mCapturedView, clampedY - oldTop); - } - - if (dx != 0 || dy != 0) { - final int clampedDx = clampedX - oldLeft; - final int clampedDy = clampedY - oldTop; - mCallback.onViewPositionChanged(mCapturedView, clampedX, clampedY, - clampedDx, clampedDy); - } - } - - /** - * Determine if the currently captured view is under the given point in the - * parent view's coordinate system. If there is no captured view this method - * will return false. - * - * @param x X position to test in the parent's coordinate system - * @param y Y position to test in the parent's coordinate system - * @return true if the captured view is under the given point, false otherwise - */ - public boolean isCapturedViewUnder(int x, int y) { - return isViewUnder(mCapturedView, x, y); - } - - /** - * Determine if the supplied view is under the given point in the - * parent view's coordinate system. - * - * @param view Child view of the parent to hit test - * @param x X position to test in the parent's coordinate system - * @param y Y position to test in the parent's coordinate system - * @return true if the supplied view is under the given point, false otherwise - */ - public boolean isViewUnder(View view, int x, int y) { - if (view == null) { - return false; - } - return x >= view.getLeft() && - x < view.getRight() && - y >= view.getTop() && - y < view.getBottom(); - } - - /** - * Find the topmost child under the given point within the parent view's coordinate system. - * The child order is determined using {@link Callback#getOrderedChildIndex(int)}. - * - * @param x X position to test in the parent's coordinate system - * @param y Y position to test in the parent's coordinate system - * @return The topmost child view under (x, y) or null if none found. - */ - public View findTopChildUnder(int x, int y) { - final int childCount = mParentView.getChildCount(); - for (int i = childCount - 1; i >= 0; i--) { - final View child = mParentView.getChildAt(mCallback.getOrderedChildIndex(i)); - if (x >= child.getLeft() && x < child.getRight() && - y >= child.getTop() && y < child.getBottom()) { - return child; - } - } - return null; - } - - private int getEdgesTouched(int x, int y) { - int result = 0; - - if (x < mParentView.getLeft() + mEdgeSize) result |= EDGE_LEFT; - if (y < mParentView.getTop() + mEdgeSize) result |= EDGE_TOP; - if (x > mParentView.getRight() - mEdgeSize) result |= EDGE_RIGHT; - if (y > mParentView.getBottom() - mEdgeSize) result |= EDGE_BOTTOM; - - return result; - } - - private boolean isValidPointerForActionMove(int pointerId) { - if (!isPointerDown(pointerId)) { - Log.e(TAG, "Ignoring pointerId=" + pointerId + " because ACTION_DOWN was not received " - + "for this pointer before ACTION_MOVE. It likely happened because " - + " ViewDragHelper did not receive all the events in the event stream."); - return false; - } - return true; - } -} \ No newline at end of file diff --git a/app/src/main/java/com/loopeer/codereader/utils/BrushMap.java b/app/src/main/java/com/loopeer/codereader/utils/BrushMap.java deleted file mode 100644 index 8172d60..0000000 --- a/app/src/main/java/com/loopeer/codereader/utils/BrushMap.java +++ /dev/null @@ -1,191 +0,0 @@ -package com.loopeer.codereader.utils; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; - -public class BrushMap { - public static HashMap> mapping; - - static { - new BrushMap(); - } - - public BrushMap() { - if (mapping == null) { - mapping = new HashMap(); - init(); - } - } - - public static final String[] FILE_BLACKLIST = new String[]{"hprof", "apk", "jar", "so"}; - - public static boolean isBlackFile(String name) { - if (name != null) return Arrays.asList(FILE_BLACKLIST).contains(name.toLowerCase()); - return false; - } - - public static void init() { - List list = new ArrayList(); - list.add("actionscript3"); - list.add("as3"); - mapping.put("AS3", list); - list = new ArrayList(); - list.add("applescript"); - list.add("scpt"); - mapping.put("AppleScript", list); - list = new ArrayList(); - list.add("bash"); - list.add("shell"); - list.add("sh"); - list.add("rc"); - list.add("conf"); - mapping.put("Bash", list); - list = new ArrayList(); - list.add("coldfusion"); - list.add("cfm"); - list.add("cf"); - mapping.put("ColdFusion", list); - list = new ArrayList(); - list.add("cpp"); - list.add("c"); - list.add("cc"); - list.add("h"); - list.add("hpp"); - mapping.put("Cpp", list); - list = new ArrayList(); - list.add("c#"); - list.add("c-sharp"); - list.add("csharp"); - list.add("cs"); - mapping.put("CSharp", list); - list = new ArrayList(); - list.add("css"); - mapping.put("Css", list); - list = new ArrayList(); - list.add("delphi"); - list.add("pascal"); - list.add("pas"); - list.add("simba"); - mapping.put("Delphi", list); - list = new ArrayList(); - list.add("diff"); - list.add("patch"); - mapping.put("Diff", list); - list = new ArrayList(); - list.add("erl"); - list.add("hrl"); - mapping.put("Erlang", list); - list = new ArrayList(); - list.add("groovy"); - mapping.put("Groovy", list); - list = new ArrayList(); - list.add("java"); - mapping.put("Java", list); - list = new ArrayList(); - list.add("jfx"); - list.add("javafx"); - mapping.put("JavaFX", list); - list = new ArrayList(); - list.add("js"); - list.add("jscript"); - list.add("javascript"); - mapping.put("JScript", list); - list = new ArrayList(); - list.add("perl"); - list.add("pl"); - mapping.put("Perl", list); - list = new ArrayList(); - list.add("php"); - mapping.put("Php", list); - list = new ArrayList(); - list.add("text"); - list.add("plain"); - list.add("rst"); - list.add("txt"); - mapping.put("Plain", list); - list = new ArrayList(); - list.add("powershell"); - list.add("ps"); - list.add("ps1"); - mapping.put("PowerShell", list); - list = new ArrayList(); - list.add("py"); - list.add("python"); - mapping.put("Python", list); - list = new ArrayList(); - list.add("ruby"); - list.add("rails"); - list.add("ror"); - mapping.put("Ruby", list); - list = new ArrayList(); - list.add("sass"); - list.add("scss"); - mapping.put("Sass", list); - list = new ArrayList(); - list.add("scala"); - mapping.put("Scala", list); - list = new ArrayList(); - list.add("sql"); - mapping.put("Sql", list); - list = new ArrayList(); - list.add("v"); - list.add("sv"); - mapping.put("Verilog", list); - list = new ArrayList(); - list.add("vb"); - list.add("vbnet"); - mapping.put("Vb", list); - list = new ArrayList(); - list.add("xml"); - list.add("xslt"); - list.add("htm"); - list.add("html"); - list.add("xhtml"); - list.add("xaml"); - list.add("iml"); - list.add("plist"); - list.add("storyboard"); - list.add("xcworkspacedata"); - list.add("xcscheme"); - mapping.put("Xml", list); - list = new ArrayList(); - list.add("gradle"); - mapping.put("Gradle", list); - list = new ArrayList(); - list.add("txt"); - list.add("bat"); - list.add("pbxproj"); - mapping.put("Txt", list); - list = new ArrayList(); - list.add("pro"); - mapping.put("Pro", list); - list = new ArrayList(); - list.add("properties"); - mapping.put("Properties", list); - list = new ArrayList(); - list.add("json"); - mapping.put("Json", list); - list = new ArrayList(); - list.add("swift"); - mapping.put("Swift", list); - list = new ArrayList(); - list.add("go"); - mapping.put("Go", list); - } - - public static String getJsFileForExtension(String paramString) { - paramString = paramString.toLowerCase(); - Iterator localIterator = mapping.entrySet().iterator(); - while (localIterator.hasNext()) { - Map.Entry localEntry = (Map.Entry) localIterator.next(); - if (((List) localEntry.getValue()).indexOf(paramString) != -1) { - return (String) localEntry.getKey(); - } - } - return null; - } -} diff --git a/app/src/main/java/com/loopeer/codereader/utils/ColorUtils.java b/app/src/main/java/com/loopeer/codereader/utils/ColorUtils.java deleted file mode 100644 index 4deab59..0000000 --- a/app/src/main/java/com/loopeer/codereader/utils/ColorUtils.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.loopeer.codereader.utils; - -import android.content.Context; -import android.support.v4.content.ContextCompat; - -public class ColorUtils { - - public static String getColorString(Context context, int res) { - return String.format("#%06X" - , 0xFFFFFF & ContextCompat.getColor(context - , res)); - } -} diff --git a/app/src/main/java/com/loopeer/codereader/utils/CustomTextUtils.java b/app/src/main/java/com/loopeer/codereader/utils/CustomTextUtils.java deleted file mode 100644 index 056772a..0000000 --- a/app/src/main/java/com/loopeer/codereader/utils/CustomTextUtils.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.loopeer.codereader.utils; - -import android.text.TextUtils; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -public class CustomTextUtils { - - public static int[] calculateTextStartEnd(String strings, String target) { - int[] result = new int[2]; - result[0] = strings.indexOf(target); - result[1] = result[0] + target.length(); - return result; - } - - - - public static boolean isEmail(String email) { - if (TextUtils.isEmpty(email)) return false; - Pattern p = Pattern.compile("\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*");//复杂匹配 - Matcher m = p.matcher(email); - return m.matches(); - } -} diff --git a/app/src/main/java/com/loopeer/codereader/utils/DeviceUtils.java b/app/src/main/java/com/loopeer/codereader/utils/DeviceUtils.java deleted file mode 100644 index affc43a..0000000 --- a/app/src/main/java/com/loopeer/codereader/utils/DeviceUtils.java +++ /dev/null @@ -1,27 +0,0 @@ -package com.loopeer.codereader.utils; - -import android.content.Context; -import android.util.DisplayMetrics; -import android.util.TypedValue; - -import com.loopeer.codereader.CodeReaderApplication; - -public class DeviceUtils { - - public static int getStatusBarHeight() { - int result = 0; - int resId = CodeReaderApplication.getAppContext() - .getResources().getIdentifier("status_bar_height", "dimen", "android"); - if (resId > 0) { - result = CodeReaderApplication.getAppContext() - .getResources().getDimensionPixelOffset(resId); - } - return result; - } - - public static float dpToPx(Context context, float dpValue) { - DisplayMetrics metrics = context.getResources().getDisplayMetrics(); - return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpValue, metrics); - } - -} diff --git a/app/src/main/java/com/loopeer/codereader/utils/DownloadFile.java b/app/src/main/java/com/loopeer/codereader/utils/DownloadFile.java deleted file mode 100644 index 5e249a6..0000000 --- a/app/src/main/java/com/loopeer/codereader/utils/DownloadFile.java +++ /dev/null @@ -1,84 +0,0 @@ -package com.loopeer.codereader.utils; - -import android.content.Context; - -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; - -public class DownloadFile { - private static final String TAG = "Download"; - private static final int BUFFER_SIZE = 8192; - - /** - * Download a file from a URL somewhere. The download is atomic; that is, it - * downloads to a temporary file, then renames it to the requested file name - * only if the download successfully completes. - * - * Returns TRUE if download succeeds, FALSE otherwise. - * - */ - public DownloadFile(Context context) { - - } - - /** - * Copy from one stream to another. Throws IOException in the event of error - * (for example, SD card is full) - * - * @param is - * Input stream. - * @param os - * Output stream. - * @param buffer - * Temporary buffer to use for copy. - * @param bufferSize - * Size of temporary buffer, in bytes. - */ - public static void copyStream(InputStream is, OutputStream os, - byte[] buffer, int bufferSize, String confid, double fileSize, - Context context) throws IOException { - double downloaded = 0; - int[] update = new int[3]; - - //Intent intent = new Intent(); - // TODO - //intent.setAction(Const.BROADCAST + confid); - - try { - for (;;) { - int count = is.read(buffer, 0, bufferSize); - downloaded += count; - if (count == -1) { - if (context != null) { - //intent.putExtra("zipcomplete", 1); - //context.sendBroadcast(intent); - } - break; - } - os.write(buffer, 0, count); - - if (context != null) { - update[0] = (int) downloaded; - update[1] = (int) fileSize; - update[2] = (int) ((downloaded / fileSize) * 100); - //intent.putExtra("zipprogress", update); - //context.sendBroadcast(intent); - } - } - - } catch (IOException e) { - throw e; - } - } - - public static String humanReadableByteCount(long bytes, boolean si) { - int unit = si ? 1000 : 1024; - if (bytes < unit) - return bytes + " B"; - int exp = (int) (Math.log(bytes) / Math.log(unit)); - String pre = (si ? "KMGTPE" : "KMGTPE").charAt(exp - 1) - + (si ? "" : "i"); - return String.format("%.2f %sB", bytes / Math.pow(unit, exp), pre); - } -} diff --git a/app/src/main/java/com/loopeer/codereader/utils/DownloadUrlParser.java b/app/src/main/java/com/loopeer/codereader/utils/DownloadUrlParser.java deleted file mode 100644 index e2e1e24..0000000 --- a/app/src/main/java/com/loopeer/codereader/utils/DownloadUrlParser.java +++ /dev/null @@ -1,89 +0,0 @@ -package com.loopeer.codereader.utils; - -import android.content.Context; -import android.text.TextUtils; - -import com.loopeer.codereader.Navigator; -import com.loopeer.codereader.model.Repo; - -import java.io.File; - -public class DownloadUrlParser { - private static final String GITHUB_REPO_URL_BASE = "https://codeload.github.com/"; - private static final String ZIP_SUFFIX = ".zip"; - - public static boolean parseGithubUrlAndDownload(Context context, String url) { - String downloadUrl = parseGithubDownloadUrl(url); - if (downloadUrl == null) return false; - String repoName = getRepoName(url); - Repo repo = new Repo(repoName - , FileCache.getInstance().getRepoAbsolutePath(repoName), downloadUrl, true, 0); - Navigator.startDownloadNewRepoService(context, repo); - return true; - } - - public static String parseGithubDownloadUrl(String url) { - if (TextUtils.isEmpty(url)) return null; - StringBuilder sb = new StringBuilder(); - String[] strings = url.split("/"); - if (strings.length < 5) return null; - sb.append(GITHUB_REPO_URL_BASE); - sb.append(strings[3]); - sb.append("/"); - if (strings.length == 5) { - if (strings[4].contains("?")) { - String[] lastName = strings[4].split("\\?"); - sb.append(lastName[0]); - sb.append("/"); - } else { - sb.append(strings[4]); - sb.append("/"); - } - sb.append("zip/master"); - return sb.toString(); - } - if (strings.length > 5) { - sb.append(strings[4]); - sb.append("/"); - if (strings.length >= 7 && strings[5].equals("tree")) { - sb.append("zip/"); - if (strings[6].contains("?")) { - String[] lastName = strings[6].split("\\?"); - sb.append(lastName[0]); - } else { - sb.append(strings[6]); - } - return sb.toString(); - } - sb.append("zip/master"); - return sb.toString(); - } - return null; - } - - public static File getRemoteRepoZipFileName(String repoName) { - return new File(FileCache.getInstance().getCacheDir(), getRepoNameZip(repoName)); - } - - public static String getRepoNameZip(String name) { - return name + ZIP_SUFFIX; - } - - public static String getRepoName(String url) { - String[] strings = url.split("/"); - StringBuilder sb = new StringBuilder(); - sb.append(strings[4].split("\\.")[0]); - if (strings.length >= 7 && strings[5].equals("tree")) { - sb.append("("); - if (strings[6].contains("?")) { - String[] lastName = strings[6].split("\\?"); - sb.append(lastName[0]); - } else { - sb.append(strings[6]); - } - sb.append(")"); - return sb.toString(); - } - return sb.toString(); - } -} diff --git a/app/src/main/java/com/loopeer/codereader/utils/FileCache.java b/app/src/main/java/com/loopeer/codereader/utils/FileCache.java deleted file mode 100644 index a6bd345..0000000 --- a/app/src/main/java/com/loopeer/codereader/utils/FileCache.java +++ /dev/null @@ -1,103 +0,0 @@ -package com.loopeer.codereader.utils; - -import android.content.Context; -import android.content.pm.PackageManager; -import android.os.Environment; - -import com.loopeer.codereader.CodeReaderApplication; -import com.loopeer.codereader.model.DirectoryNode; - -import java.io.File; -import java.util.ArrayList; -import java.util.Collections; - -public class - - -FileCache { - private static String EXTERNAL_STORAGE_PERMISSION = "android.permission.WRITE_EXTERNAL_STORAGE"; - private static FileCache instance; - private static String cachePath = Environment.getExternalStorageDirectory() + "/CodeReader/repo/"; - private File cacheDir; - public final String cacheDirPath = "/repo/"; - - private FileCache() { - if (hasSDCard() && hasExternalStoragePermission(CodeReaderApplication.getAppContext())) { - cacheDir = createFilePath(cachePath); - } else { - cacheDir = createFilePath(CodeReaderApplication.getAppContext().getCacheDir() + cacheDirPath); - } - } - - private File createFilePath(String filePath) { - return createFilePath(new File(filePath)); - } - - private File createFilePath(File file) { - if (!file.exists()) { - file.mkdirs();// 按照文件夹路径创建文件夹 - } - return file; - } - - public static FileCache getInstance() { - if (null == instance) - instance = new FileCache(); - return instance; - } - - public boolean hasSDCard() { - return Environment.getExternalStorageState().equals(android.os.Environment.MEDIA_MOUNTED); - } - - public DirectoryNode getFileDirectoryNode() { - File file = new File(cacheDir, "CardStackView"); - if (file.listFiles() == null || file.listFiles().length == 0) return null; - return getFileDirectory(file); - } - - public File getCacheDir() { - return cacheDir; - } - - public String getRepoAbsolutePath(String repoName) { - return getCacheDir().getPath() + File.separator + repoName; - } - - public static DirectoryNode getFileDirectory(File file) { - if (file == null) return null; - DirectoryNode directoryNode = new DirectoryNode(); - directoryNode.name = file.getName(); - directoryNode.absolutePath = file.getAbsolutePath(); - if (file.isDirectory()) { - directoryNode.isDirectory = true; - directoryNode.pathNodes = new ArrayList<>(); - for (File childFile : file.listFiles()) { - if (childFile.getName().startsWith(".") || childFile.getName().startsWith("_")) continue; - DirectoryNode childNode = getFileDirectory(childFile); - directoryNode.pathNodes.add(childNode); - } - if (!directoryNode.pathNodes.isEmpty()) { - Collections.sort(directoryNode.pathNodes); - } - } - return directoryNode; - } - - public static boolean hasExternalStoragePermission(Context context) { - int perm = context.checkCallingOrSelfPermission(EXTERNAL_STORAGE_PERMISSION); - return perm == PackageManager.PERMISSION_GRANTED; - } - - public static void deleteFilesByDirectory(File directory) { - if (directory != null && directory.exists() && directory.list() != null) { - for (File item : directory.listFiles()) { - if (item.isDirectory()) { - deleteFilesByDirectory(item); - } else { - item.delete(); - } - } - } - } -} diff --git a/app/src/main/java/com/loopeer/codereader/utils/FileTypeUtils.java b/app/src/main/java/com/loopeer/codereader/utils/FileTypeUtils.java deleted file mode 100644 index afab2a6..0000000 --- a/app/src/main/java/com/loopeer/codereader/utils/FileTypeUtils.java +++ /dev/null @@ -1,205 +0,0 @@ -package com.loopeer.codereader.utils; - -import java.util.HashMap; -import java.util.Iterator; - -public class FileTypeUtils { - - // comma separated list of all file extensions supported by the media scanner - public static String sFileExtensions; - - // Audio file types - public static final int FILE_TYPE_MP3 = 1; - public static final int FILE_TYPE_M4A = 2; - public static final int FILE_TYPE_WAV = 3; - public static final int FILE_TYPE_AMR = 4; - public static final int FILE_TYPE_AWB = 5; - public static final int FILE_TYPE_WMA = 6; - public static final int FILE_TYPE_OGG = 7; - private static final int FIRST_AUDIO_FILE_TYPE = FILE_TYPE_MP3; - private static final int LAST_AUDIO_FILE_TYPE = FILE_TYPE_OGG; - - // MIDI file types - public static final int FILE_TYPE_MID = 11; - public static final int FILE_TYPE_SMF = 12; - public static final int FILE_TYPE_IMY = 13; - private static final int FIRST_MIDI_FILE_TYPE = FILE_TYPE_MID; - private static final int LAST_MIDI_FILE_TYPE = FILE_TYPE_IMY; - - // Video file types - public static final int FILE_TYPE_MP4 = 21; - public static final int FILE_TYPE_M4V = 22; - public static final int FILE_TYPE_3GPP = 23; - public static final int FILE_TYPE_3GPP2 = 24; - public static final int FILE_TYPE_WMV = 25; - private static final int FIRST_VIDEO_FILE_TYPE = FILE_TYPE_MP4; - private static final int LAST_VIDEO_FILE_TYPE = FILE_TYPE_WMV; - - // Image file types - public static final int FILE_TYPE_JPEG = 31; - public static final int FILE_TYPE_GIF = 32; - public static final int FILE_TYPE_PNG = 33; - public static final int FILE_TYPE_BMP = 34; - public static final int FILE_TYPE_WBMP = 35; - private static final int FIRST_IMAGE_FILE_TYPE = FILE_TYPE_JPEG; - private static final int LAST_IMAGE_FILE_TYPE = FILE_TYPE_WBMP; - - // Playlist file types - public static final int FILE_TYPE_M3U = 41; - public static final int FILE_TYPE_PLS = 42; - public static final int FILE_TYPE_WPL = 43; - private static final int FIRST_PLAYLIST_FILE_TYPE = FILE_TYPE_M3U; - private static final int LAST_PLAYLIST_FILE_TYPE = FILE_TYPE_WPL; - - // MarkDown file types - public static final int FILE_TYPE_MARKDOWN = 44; - public static final int FILE_TYPE_MD = 45; - private static final int FIRST_MARKDOWNLIST_FILE_TYPE = FILE_TYPE_MARKDOWN; - private static final int LAST_MARKDOWNLIST_FILE_TYPE = FILE_TYPE_MD; - - //静态内部类 - static class MediaFileType { - - int fileType; - String mimeType; - - MediaFileType(int fileType, String mimeType) { - this.fileType = fileType; - this.mimeType = mimeType; - } - } - - private static HashMap sFileTypeMap - = new HashMap(); - private static HashMap sMimeTypeMap - = new HashMap(); - - static void addFileType(String extension, int fileType, String mimeType) { - sFileTypeMap.put(extension, new MediaFileType(fileType, mimeType)); - sMimeTypeMap.put(mimeType, new Integer(fileType)); - } - - static { - addFileType("MP3", FILE_TYPE_MP3, "audio/mpeg"); - addFileType("M4A", FILE_TYPE_M4A, "audio/mp4"); - addFileType("WAV", FILE_TYPE_WAV, "audio/x-wav"); - addFileType("AMR", FILE_TYPE_AMR, "audio/amr"); - addFileType("AWB", FILE_TYPE_AWB, "audio/amr-wb"); - addFileType("WMA", FILE_TYPE_WMA, "audio/x-ms-wma"); - addFileType("OGG", FILE_TYPE_OGG, "application/ogg"); - - addFileType("MID", FILE_TYPE_MID, "audio/midi"); - addFileType("XMF", FILE_TYPE_MID, "audio/midi"); - addFileType("RTTTL", FILE_TYPE_MID, "audio/midi"); - addFileType("SMF", FILE_TYPE_SMF, "audio/sp-midi"); - addFileType("IMY", FILE_TYPE_IMY, "audio/imelody"); - - addFileType("MP4", FILE_TYPE_MP4, "video/mp4"); - addFileType("M4V", FILE_TYPE_M4V, "video/mp4"); - addFileType("3GP", FILE_TYPE_3GPP, "video/3gpp"); - addFileType("3GPP", FILE_TYPE_3GPP, "video/3gpp"); - addFileType("3G2", FILE_TYPE_3GPP2, "video/3gpp2"); - addFileType("3GPP2", FILE_TYPE_3GPP2, "video/3gpp2"); - addFileType("WMV", FILE_TYPE_WMV, "video/x-ms-wmv"); - - addFileType("JPG", FILE_TYPE_JPEG, "image/jpeg"); - addFileType("JPEG", FILE_TYPE_JPEG, "image/jpeg"); - addFileType("GIF", FILE_TYPE_GIF, "image/gif"); - addFileType("PNG", FILE_TYPE_PNG, "image/png"); - addFileType("BMP", FILE_TYPE_BMP, "image/x-ms-bmp"); - addFileType("WBMP", FILE_TYPE_WBMP, "image/vnd.wap.wbmp"); - - addFileType("M3U", FILE_TYPE_M3U, "audio/x-mpegurl"); - addFileType("PLS", FILE_TYPE_PLS, "audio/x-scpls"); - addFileType("WPL", FILE_TYPE_WPL, "application/vnd.ms-wpl"); - - addFileType("MD", FILE_TYPE_MD, "text/markdown"); - addFileType("MARKDOWN", FILE_TYPE_MARKDOWN, "text/markdown"); - - // compute file extensions list for native Media Scanner - StringBuilder builder = new StringBuilder(); - Iterator iterator = sFileTypeMap.keySet().iterator(); - - while (iterator.hasNext()) { - if (builder.length() > 0) { - builder.append(','); - } - builder.append(iterator.next()); - } - sFileExtensions = builder.toString(); - } - - public static final String UNKNOWN_STRING = ""; - - private static boolean isAudioFileType(int fileType) { - return ((fileType >= FIRST_AUDIO_FILE_TYPE && - fileType <= LAST_AUDIO_FILE_TYPE) || - (fileType >= FIRST_MIDI_FILE_TYPE && - fileType <= LAST_MIDI_FILE_TYPE)); - } - - private static boolean isVideoFileType(int fileType) { - return (fileType >= FIRST_VIDEO_FILE_TYPE && - fileType <= LAST_VIDEO_FILE_TYPE); - } - - private static boolean isImageFileType(int fileType) { - return (fileType >= FIRST_IMAGE_FILE_TYPE && - fileType <= LAST_IMAGE_FILE_TYPE); - } - - private static boolean isPlayListFileType(int fileType) { - return (fileType >= FIRST_PLAYLIST_FILE_TYPE && - fileType <= LAST_PLAYLIST_FILE_TYPE); - } - - private static boolean isMarkDownFileType(int fileType) { - return (fileType >= FIRST_MARKDOWNLIST_FILE_TYPE && - fileType <= LAST_MARKDOWNLIST_FILE_TYPE); - } - - public static MediaFileType getFileType(String path) { - int lastDot = path.lastIndexOf("."); - if (lastDot < 0) - return null; - return sFileTypeMap.get(path.substring(lastDot + 1).toUpperCase()); - } - - public static boolean isVideoFileType(String path) { - MediaFileType type = getFileType(path); - if (null != type) { - return isVideoFileType(type.fileType); - } - return false; - } - - public static boolean isAudioFileType(String path) { - MediaFileType type = getFileType(path); - if (null != type) { - return isAudioFileType(type.fileType); - } - return false; - } - - public static boolean isImageFileType(String path) { - MediaFileType type = getFileType(path); - if (null != type) { - return isImageFileType(type.fileType); - } - return false; - } - - public static int getFileTypeForMimeType(String mimeType) { - Integer value = sMimeTypeMap.get(mimeType); - return (value == null ? 0 : value.intValue()); - } - - public static boolean isMdFileType(String path) { - MediaFileType type = getFileType(path); - if (null != type) { - return isMarkDownFileType(type.fileType); - } - return false; - } - -} diff --git a/app/src/main/java/com/loopeer/codereader/utils/HtmlParser.java b/app/src/main/java/com/loopeer/codereader/utils/HtmlParser.java deleted file mode 100644 index 644f320..0000000 --- a/app/src/main/java/com/loopeer/codereader/utils/HtmlParser.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.loopeer.codereader.utils; - -import android.content.Context; -import android.os.Build; - -import com.loopeer.codereader.R; - -import java.io.IOException; -import java.io.InputStream; - -public class HtmlParser { - public static String buildHtmlContent(Context context, String paramString1, String jsFile - , String fileName) { - for (; ; ) { - try { - InputStream inputStream = context.getAssets().open("code.html"); - Object localObject = new byte[inputStream.available()]; - inputStream.read((byte[]) localObject); - inputStream.close(); - localObject = new String((byte[]) localObject); - StringBuilder localStringBuilder = new StringBuilder(); - localStringBuilder.append("SyntaxHighlighter.defaults['auto-links'] = false;"); - localStringBuilder.append("SyntaxHighlighter.defaults['toolbar'] = false;"); - localStringBuilder.append("SyntaxHighlighter.defaults['wrap-lines'] = false;"); - localStringBuilder.append("SyntaxHighlighter.defaults['quick-code'] = false;"); - if (!PrefUtils.getPrefDisplayLineNumber(context)) { - localStringBuilder.append("SyntaxHighlighter.defaults['gutter'] = false;"); - } - localStringBuilder.append("SyntaxHighlighter.all();"); - String temp = ""; - if (Build.VERSION.SDK_INT < 14) { - temp = "$('.syntaxhighlighter').css('overflow', 'visible !important');"; - } - jsFile = ((String) localObject) - .replace("!FONT_SIZE!" - , String.format("" - , new Object[]{Float.valueOf(PrefUtils.getPrefFontSize(context))})) - .replace("!FILENAME!" - , fileName) - .replace("!BRUSHJSFILE!", jsFile) - .replace("!SYNTAXHIGHLIGHTER!" - , localStringBuilder.toString()) - .replace("!JS_FIX_HSCROLL!", temp); - temp = ""; - return jsFile - .replace("!STYLE_MENLO!", PrefUtils.getPrefMenlofont(context) ? temp : "") - .replace("!THEME!", PrefUtils.getPrefTheme(context)) - .replace("!CODE!", paramString1) - - .replace("!WINDOW_BACK_GROUND_COLOR!" - , ColorUtils.getColorString(context, R.color.code_read_background_color)); - } catch (IOException e) { - throw new RuntimeException(e); - } finally { - - } - } - } -} diff --git a/app/src/main/java/com/loopeer/codereader/utils/PageLinkParser.java b/app/src/main/java/com/loopeer/codereader/utils/PageLinkParser.java deleted file mode 100644 index 0cd653c..0000000 --- a/app/src/main/java/com/loopeer/codereader/utils/PageLinkParser.java +++ /dev/null @@ -1,99 +0,0 @@ -package com.loopeer.codereader.utils; - -import android.text.TextUtils; - -import retrofit2.Response; - -public class PageLinkParser { - - private static final String SPLIT_LINKS = ","; - private static final String SPLIT_LINK_PARAM = ";"; - - private static final String REL_KEY = "rel"; - - private static final String REL_VALUE_LAST = "last"; - private static final String REL_VALUE_NEXT = "next"; - private static final String REL_VALUE_FIRST = "first"; - private static final String REL_VALUE_PREV = "prev"; - - private int first; - private int last; - private int next; - private int prev; - - private int remain; - - public PageLinkParser(Response response) { - remain = Integer.parseInt(response.headers().get("X-RateLimit-Remaining")); - - String linkHeader = response.headers().get("Link"); - if (TextUtils.isEmpty(linkHeader)) - return; - - String[] links = linkHeader.split(SPLIT_LINKS); - for (String link : links) { - String[] params = link.split(SPLIT_LINK_PARAM); - if (params.length < 2) - continue; - - String url = params[0].trim(); - if (!url.startsWith("<") || !url.endsWith(">")) - continue; - url = url.substring(1, url.length() - 1); - - for (int i = 1; i < params.length; i++) { - String[] rel = params[i].trim().split("="); - if (rel.length < 2 || !REL_KEY.equals(rel[0])) - continue; - - String relValue = rel[1]; - if (relValue.startsWith("\"") && relValue.endsWith("\"")) - relValue = relValue.substring(1, relValue.length() - 1); - - if (REL_VALUE_FIRST.equals(relValue)) - first = getParam(url); - else if (REL_VALUE_LAST.equals(relValue)) - last = getParam(url); - else if (REL_VALUE_NEXT.equals(relValue)) - next = getParam(url); - else if (REL_VALUE_PREV.equals(relValue)) - prev = getParam(url); - } - } - } - - private int getParam(String url) { - if (TextUtils.isEmpty(url)) - return 0; - final String[] params = url.split("&"); - for (String param : params) { - final String[] parts = param.split("="); - if (parts.length != 2) - continue; - if (!"page".equals(parts[0])) - continue; - return Integer.parseInt(parts[1]); - } - return 0; - } - - public int getFirst() { - return first; - } - - public int getLast() { - return last; - } - - public int getNext() { - return next; - } - - public int getPrev() { - return prev; - } - - public int getRemain() { - return remain; - } -} diff --git a/app/src/main/java/com/loopeer/codereader/utils/PrefUtils.java b/app/src/main/java/com/loopeer/codereader/utils/PrefUtils.java deleted file mode 100644 index 50af070..0000000 --- a/app/src/main/java/com/loopeer/codereader/utils/PrefUtils.java +++ /dev/null @@ -1,54 +0,0 @@ -package com.loopeer.codereader.utils; - -import android.content.Context; -import android.content.SharedPreferences; -import android.preference.PreferenceManager; - -public class PrefUtils { - - public static final String PREF_FONT_SIZE = "pref_font_size"; - public static final String PREF_DISPLAY_LINE_NUMBER = "pref_display_line_number"; - public static final String PREF_MENLO_FONT = "pref_menlo_font"; - public static final String PREF_THEME = "pref_theme"; - - public static float getPrefFontSize(final Context context) { - SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); - return sp.getFloat(PREF_FONT_SIZE, 12f); - } - - public static void setPrefFontSize(final Context context, float size) { - SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); - sp.edit().putFloat(PREF_FONT_SIZE, size).commit(); - } - - public static String getPrefTheme(final Context context) { - SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); - return sp.getString(PREF_THEME, "Default"); - } - - public static void setPrefTheme(final Context context, String theme) { - SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); - sp.edit().putString(PREF_THEME, theme).commit(); - } - - public static boolean getPrefMenlofont(final Context context) { - SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); - return sp.getBoolean(PREF_MENLO_FONT, true); - } - - public static void setPrefMenlofont(final Context context, boolean b) { - SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); - sp.edit().putBoolean(PREF_MENLO_FONT, b).commit(); - } - - public static boolean getPrefDisplayLineNumber(final Context context) { - SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); - return sp.getBoolean(PREF_DISPLAY_LINE_NUMBER, true); - } - - public static void setPrefDisplayLineNumber(final Context context, boolean b) { - SharedPreferences sp = PreferenceManager.getDefaultSharedPreferences(context); - sp.edit().putBoolean(PREF_DISPLAY_LINE_NUMBER, b).commit(); - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/loopeer/codereader/utils/RxBus.java b/app/src/main/java/com/loopeer/codereader/utils/RxBus.java deleted file mode 100644 index e3b631a..0000000 --- a/app/src/main/java/com/loopeer/codereader/utils/RxBus.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.loopeer.codereader.utils; - -import rx.Observable; -import rx.subjects.PublishSubject; -import rx.subjects.SerializedSubject; -import rx.subjects.Subject; - -public class RxBus { - - private static volatile RxBus mDefaultInstance; - - private RxBus() { - } - - public static RxBus getInstance() { - if (mDefaultInstance == null) { - synchronized (RxBus.class) { - if (mDefaultInstance == null) { - mDefaultInstance = new RxBus(); - } - } - } - return mDefaultInstance; - } - - private final Subject _bus = new SerializedSubject<>(PublishSubject.create()); - - public void send(Object o) { - _bus.onNext(o); - } - - public Observable toObservable() { - return _bus; - } -} \ No newline at end of file diff --git a/app/src/main/java/com/loopeer/codereader/utils/ThemeUtils.java b/app/src/main/java/com/loopeer/codereader/utils/ThemeUtils.java deleted file mode 100644 index 651bf54..0000000 --- a/app/src/main/java/com/loopeer/codereader/utils/ThemeUtils.java +++ /dev/null @@ -1,16 +0,0 @@ -package com.loopeer.codereader.utils; - -import android.content.Context; -import android.support.v7.app.AppCompatDelegate; - -public class ThemeUtils { - public static final String THEME_DAY = "Default"; - public static final String THEME_NIGHT = "Night"; - - @AppCompatDelegate.NightMode - public static int getCurrentNightMode(Context context) { - return PrefUtils.getPrefTheme(context).equals(THEME_DAY) - ? AppCompatDelegate.MODE_NIGHT_NO - : AppCompatDelegate.MODE_NIGHT_YES; - } -} diff --git a/app/src/main/java/com/loopeer/codereader/utils/Unzip.java b/app/src/main/java/com/loopeer/codereader/utils/Unzip.java deleted file mode 100644 index 5881f9e..0000000 --- a/app/src/main/java/com/loopeer/codereader/utils/Unzip.java +++ /dev/null @@ -1,113 +0,0 @@ -package com.loopeer.codereader.utils; - -import android.content.Context; - -import java.io.BufferedOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.OutputStream; -import java.util.zip.ZipEntry; -import java.util.zip.ZipInputStream; - -public class Unzip { - private static final int BUFFER_SIZE = 8192; - - private String mZipFile; - private String mLocation; - private byte[] mBuffer; - private Context mContext; - - /** - * Constructor. - * - * @param zipFile Fully-qualified path to .zip file - * @param location Fully-qualified path to folder where files should be written. - * Path must have a trailing slash. - */ - public Unzip(String zipFile, String location, Context context) { - mZipFile = zipFile; - mLocation = location; - mBuffer = new byte[BUFFER_SIZE]; - mContext = context; - dirChecker(null); - } - - public void DecompressZip() { - FileInputStream fin = null; - ZipInputStream zin = null; - OutputStream fout = null; - File outputDir = new File(mLocation); - File tmp = null; - try { - fin = new FileInputStream(mZipFile); - zin = new ZipInputStream(fin); - ZipEntry ze; - while ((ze = zin.getNextEntry()) != null) { - if (ze.isDirectory()) { - dirChecker(ze); - } else { - tmp = File.createTempFile("decomp", ".tmp", outputDir); - fout = new BufferedOutputStream(new FileOutputStream(tmp)); - DownloadFile.copyStream(zin, fout, mBuffer, BUFFER_SIZE, - null, 0, null); - - zin.closeEntry(); - fout.close(); - fout = null; - tmp.renameTo(new File(getPathSaveName(ze))); - tmp = null; - } - } - zin.close(); - zin = null; - } catch (IOException e) { - throw new RuntimeException(e); - } finally { - if (tmp != null) { - try { - tmp.delete(); - } catch (Exception ignore) { - ; - } - } - if (fout != null) { - try { - fout.close(); - } catch (Exception ignore) { - ; - } - } - if (zin != null) { - try { - zin.closeEntry(); - } catch (Exception ignore) { - ; - } - } - if (fin != null) { - try { - fin.close(); - } catch (Exception ignore) { - ; - } - } - } - } - - private String getPathSaveName(ZipEntry ze) { - if (ze == null) { - return mLocation; - } - String zeName = ze.getName(); - return mLocation + File.separator + zeName.substring(zeName.indexOf("/") + 1, zeName.length()); - } - - private void dirChecker(ZipEntry ze) { - File f = new File(getPathSaveName(ze)); - if (!f.isDirectory()) { - f.mkdirs(); - } - } -} \ No newline at end of file diff --git a/app/src/main/res/layout/activity_about.xml b/app/src/main/res/layout/activity_about.xml deleted file mode 100644 index 7e50400..0000000 --- a/app/src/main/res/layout/activity_about.xml +++ /dev/null @@ -1,74 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/activity_code_read.xml b/app/src/main/res/layout/activity_code_read.xml deleted file mode 100644 index d23f008..0000000 --- a/app/src/main/res/layout/activity_code_read.xml +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml deleted file mode 100644 index a2d65f6..0000000 --- a/app/src/main/res/layout/activity_main.xml +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/activity_setting.xml b/app/src/main/res/layout/activity_setting.xml deleted file mode 100644 index 443e147..0000000 --- a/app/src/main/res/layout/activity_setting.xml +++ /dev/null @@ -1,161 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/activity_simple_web.xml b/app/src/main/res/layout/activity_simple_web.xml deleted file mode 100644 index 28d8fb9..0000000 --- a/app/src/main/res/layout/activity_simple_web.xml +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_code_read.xml b/app/src/main/res/layout/fragment_code_read.xml deleted file mode 100644 index 52836e1..0000000 --- a/app/src/main/res/layout/fragment_code_read.xml +++ /dev/null @@ -1,47 +0,0 @@ - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/view_list_repo_item_content.xml b/app/src/main/res/layout/view_list_repo_item_content.xml deleted file mode 100644 index 81733ae..0000000 --- a/app/src/main/res/layout/view_list_repo_item_content.xml +++ /dev/null @@ -1,70 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/build.gradle b/build.gradle index 3491e8e..c1b5520 100644 --- a/build.gradle +++ b/build.gradle @@ -1,15 +1,19 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. buildscript { + ext.kotlin_version = '1.1.4' repositories { jcenter() + google() mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:2.1.3' + + classpath 'com.android.tools.build:gradle:2.3.3' classpath 'me.tatarka:gradle-retrolambda:3.2.5' classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' classpath 'com.squareup.sqldelight:gradle-plugin:0.4.4' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } @@ -18,6 +22,7 @@ buildscript { allprojects { repositories { jcenter() + google() mavenCentral() } } diff --git a/app/.gitignore b/code-reader-kt/.gitignore similarity index 100% rename from app/.gitignore rename to code-reader-kt/.gitignore diff --git a/code-reader-kt/build.gradle b/code-reader-kt/build.gradle new file mode 100644 index 0000000..d1c5703 --- /dev/null +++ b/code-reader-kt/build.gradle @@ -0,0 +1,87 @@ +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' +apply plugin: 'kotlin-kapt' +apply plugin: 'me.tatarka.retrolambda' +apply plugin: 'com.squareup.sqldelight' + +android { + compileSdkVersion 26 + buildToolsVersion "26.0.1" + + defaultConfig { + applicationId "com.loopeer.codereader" + minSdkVersion 15 + targetSdkVersion 26 + versionCode version_code as int + versionName version_name + multiDexEnabled true + testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" + + } + + signingConfigs { + release { + Properties properties = new Properties() + FileInputStream fis = new FileInputStream("$project.rootDir/keystore.properties") + properties.load(fis) + fis.close() + + keyAlias = properties.key_alias + storeFile = file(properties.store_file) + + storePassword = properties.store_password + keyPassword = properties.key_password + } + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + + dataBinding { + enabled = true + } + compileOptions { + targetCompatibility 1.8 + sourceCompatibility 1.8 + } +} + + + +dependencies { + compile fileTree(dir: 'libs', include: ['*.jar']) + androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { + exclude group: 'com.android.support', module: 'support-annotations' + }) + compile 'com.android.support:appcompat-v7:' + support_version + compile 'com.android.support:design:' + support_version + compile 'com.android.support.constraint:constraint-layout:1.0.2' + testCompile 'junit:junit:4.12' + compile "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version" + compile project(':libraries:directoryandfilechooser') + compile project(':libraries:itemtouchhelperextension') + compile project(':libraries:markdownj') + compile 'io.reactivex:rxjava:1.1.8' + compile 'com.facebook.stetho:stetho:1.1.0' + compile 'com.squareup.retrofit2:retrofit:' + retrofit + compile 'com.squareup.retrofit2:converter-gson:' + retrofit + compile 'com.squareup.retrofit2:adapter-rxjava:' + retrofit + compile 'com.squareup.okhttp3:logging-interceptor:3.4.1' + compile 'io.reactivex:rxjava:' + rxjava + compile 'io.reactivex:rxandroid:' + rxandroid + compile 'com.github.bumptech.glide:glide:' + glide + kapt 'com.android.databinding:compiler:3.0.0-beta2' + provided 'com.google.auto.value:auto-value:' + auto_value + provided 'com.jakewharton.auto.value:auto-value-annotations:1.2-update1' +} +repositories { + mavenCentral() +} +kapt { + generateStubs = true //这句很重要,kotlin中使用databinding +} diff --git a/app/proguard-rules.pro b/code-reader-kt/proguard-rules.pro similarity index 62% rename from app/proguard-rules.pro rename to code-reader-kt/proguard-rules.pro index 80567a9..c30f736 100644 --- a/app/proguard-rules.pro +++ b/code-reader-kt/proguard-rules.pro @@ -1,6 +1,6 @@ # Add project specific ProGuard rules here. # By default, the flags in this file are appended to flags specified -# in /Users/loopeer/Library/Android/sdk/tools/proguard/proguard-android.txt +# in C:\Users\loopeer\AppData\Local\Android\sdk1/tools/proguard/proguard-android.txt # You can edit the include path and order by changing the proguardFiles # directive in build.gradle. # @@ -15,3 +15,11 @@ #-keepclassmembers class fqcn.of.javascript.interface.for.webview { # public *; #} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/code-reader-kt/src/androidTest/java/com/loopeer/codereaderkt/ExampleInstrumentedTest.java b/code-reader-kt/src/androidTest/java/com/loopeer/codereaderkt/ExampleInstrumentedTest.java new file mode 100644 index 0000000..5613515 --- /dev/null +++ b/code-reader-kt/src/androidTest/java/com/loopeer/codereaderkt/ExampleInstrumentedTest.java @@ -0,0 +1,26 @@ +package com.loopeer.codereaderkt; + +import android.content.Context; +import android.support.test.InstrumentationRegistry; +import android.support.test.runner.AndroidJUnit4; + +import org.junit.Test; +import org.junit.runner.RunWith; + +import static org.junit.Assert.*; + +/** + * Instrumentation test, which will execute on an Android device. + * + * @see Testing documentation + */ +@RunWith(AndroidJUnit4.class) +public class ExampleInstrumentedTest { + @Test + public void useAppContext() throws Exception { + // Context of the app under test. + Context appContext = InstrumentationRegistry.getTargetContext(); + + assertEquals("com.loopeer.codereaderkt", appContext.getPackageName()); + } +} diff --git a/app/src/main/AndroidManifest.xml b/code-reader-kt/src/main/AndroidManifest.xml similarity index 67% rename from app/src/main/AndroidManifest.xml rename to code-reader-kt/src/main/AndroidManifest.xml index 30d2968..eb74d24 100644 --- a/app/src/main/AndroidManifest.xml +++ b/code-reader-kt/src/main/AndroidManifest.xml @@ -1,14 +1,16 @@ + package="com.loopeer.codereaderkt"> - - + + + + - - - + + + - + - + + + android:name=".ui.activity.AddRepoActivity" + android:label="@string/title_activity_add_repo"> + + + + android:name=".ui.activity.SettingActivity" + android:label="@string/title_activity_setting"> + + + - - - + android:name=".ui.activity.LoginActivity" + android:label="@string/login_label"> + + + + android:name="android.support.PARENT_ACTIVITY" + android:value=".ui.activity.MainActivity" /> + + android:value=".ui.activity.MainActivity" /> - + + + + - - + + + + - - + + + + + - + + + + + + + @@ -74,27 +114,16 @@ - - - - + - - - - + android:name="android.support.FILE_PROVIDER_PATHS" + android:resource="@xml/path" /> + + \ No newline at end of file diff --git a/app/src/main/assets/code.html b/code-reader-kt/src/main/assets/code.html old mode 100755 new mode 100644 similarity index 97% rename from app/src/main/assets/code.html rename to code-reader-kt/src/main/assets/code.html index 7af619f..559a3ec --- a/app/src/main/assets/code.html +++ b/code-reader-kt/src/main/assets/code.html @@ -1,41 +1,41 @@ - - - - !FILENAME! - - - - - - - - - - - - - - - !FONT_SIZE! - !STYLE_MENLO! - - - - - !CODE! - - + + + + !FILENAME! + + + + + + + + + + + + + + + !FONT_SIZE! + !STYLE_MENLO! + + + + + !CODE! + + diff --git a/app/src/main/assets/fonts/Menlo-Regular.ttf b/code-reader-kt/src/main/assets/fonts/Menlo-Regular.ttf similarity index 100% rename from app/src/main/assets/fonts/Menlo-Regular.ttf rename to code-reader-kt/src/main/assets/fonts/Menlo-Regular.ttf diff --git a/app/src/main/assets/jquery-1.7.2.min.js b/code-reader-kt/src/main/assets/jquery-1.7.2.min.js old mode 100755 new mode 100644 similarity index 100% rename from app/src/main/assets/jquery-1.7.2.min.js rename to code-reader-kt/src/main/assets/jquery-1.7.2.min.js diff --git a/app/src/main/assets/scripts/shBrushAS3.js b/code-reader-kt/src/main/assets/scripts/shBrushAS3.js old mode 100755 new mode 100644 similarity index 100% rename from app/src/main/assets/scripts/shBrushAS3.js rename to code-reader-kt/src/main/assets/scripts/shBrushAS3.js diff --git a/app/src/main/assets/scripts/shBrushAppleScript.js b/code-reader-kt/src/main/assets/scripts/shBrushAppleScript.js old mode 100755 new mode 100644 similarity index 100% rename from app/src/main/assets/scripts/shBrushAppleScript.js rename to code-reader-kt/src/main/assets/scripts/shBrushAppleScript.js diff --git a/code-reader-kt/src/main/assets/scripts/shBrushArduino.js b/code-reader-kt/src/main/assets/scripts/shBrushArduino.js new file mode 100644 index 0000000..045030f --- /dev/null +++ b/code-reader-kt/src/main/assets/scripts/shBrushArduino.js @@ -0,0 +1,68 @@ +/** + * SyntaxHighlighter + * http://alexgorbatchev.com/SyntaxHighlighter + * + * SyntaxHighlighter is donationware. If you are using it, please donate. + * http://alexgorbatchev.com/SyntaxHighlighter/donate.html + * + * @version + * 3.0.83 (July 02 2010) + * + * @copyright + * Copyright (C) 2004-2010 Alex Gorbatchev. + * + * @license + * Dual licensed under the MIT and GPL licenses. + */ +;(function() +{ + // CommonJS + typeof(require) != 'undefined' ? SyntaxHighlighter = require('shCore').SyntaxHighlighter : null; + + function Brush() + { + // Copyright 2006 Shin, YoungJin + // Arduino addon 2016 Dubkov Ilya + + var datatypes = 'void boolean char unsigned signed byte int word long ' + + 'short double float string String' + + var keywords = 'break case class const private public protected default do ' + + 'else enum extern if for goto inline namespace new ' + + 'return sizeof static struct switch this true false typedef union ' + + 'using virtual void volatile while ' + + 'INPUT INPUT_PULLUP OUTPUT HIGH LOW LED_BUILTIN PROGMEM'; + + var functions = + 'Serial Wire SPI Stream setup loop digitalWrite pinMode digitalRead analogWrite ' + + 'analogRead analogReadResolution analogWriteResolution analogReference ' + + 'delay begin available println print write read min max abs constrain ' + + 'map pow sqrt random randomSeed millis micros delayMicroseconds attachInterrupt ' + + 'detachInterrupt interrupts noInterrupts pulseln shiftln shiftOut ' + + 'tone noTone sin cos tan isAlphaNumeric isAlpha isAscii isWhitespace isControl ' + + 'isDigit isGraph isLowerCase isPrintable isPunct isSpace isUpperCase ' + + 'isHexadecimalDigit lowByte highByte bitRead bitWrite bitSet bitClear bit ' + + 'char byte int long float word '; + + + + this.regexList = [ + { regex: SyntaxHighlighter.regexLib.singleLineCComments, css: 'comments' }, // one line comments + { regex: SyntaxHighlighter.regexLib.multiLineCComments, css: 'comments' }, // multiline comments + { regex: SyntaxHighlighter.regexLib.doubleQuotedString, css: 'string' }, // strings + { regex: SyntaxHighlighter.regexLib.singleQuotedString, css: 'string' }, // strings + { regex: /^ *#.*/gm, css: 'preprocessor' }, + { regex: new RegExp(this.getKeywords(datatypes), 'gm'), css: 'color1 bold' }, + { regex: new RegExp(this.getKeywords(functions), 'gm'), css: 'functions bold' }, + { regex: new RegExp(this.getKeywords(keywords), 'gm'), css: 'keyword bold' } + ]; + }; + + Brush.prototype = new SyntaxHighlighter.Highlighter(); + Brush.aliases = ['arduino', 'Arduino']; + + SyntaxHighlighter.brushes.Arduino = Brush; + + // CommonJS + typeof(exports) != 'undefined' ? exports.Brush = Brush : null; +})(); \ No newline at end of file diff --git a/app/src/main/assets/scripts/shBrushBash.js b/code-reader-kt/src/main/assets/scripts/shBrushBash.js old mode 100755 new mode 100644 similarity index 100% rename from app/src/main/assets/scripts/shBrushBash.js rename to code-reader-kt/src/main/assets/scripts/shBrushBash.js diff --git a/app/src/main/assets/scripts/shBrushCSharp.js b/code-reader-kt/src/main/assets/scripts/shBrushCSharp.js old mode 100755 new mode 100644 similarity index 100% rename from app/src/main/assets/scripts/shBrushCSharp.js rename to code-reader-kt/src/main/assets/scripts/shBrushCSharp.js diff --git a/app/src/main/assets/scripts/shBrushColdFusion.js b/code-reader-kt/src/main/assets/scripts/shBrushColdFusion.js old mode 100755 new mode 100644 similarity index 100% rename from app/src/main/assets/scripts/shBrushColdFusion.js rename to code-reader-kt/src/main/assets/scripts/shBrushColdFusion.js diff --git a/app/src/main/assets/scripts/shBrushCpp.js b/code-reader-kt/src/main/assets/scripts/shBrushCpp.js old mode 100755 new mode 100644 similarity index 100% rename from app/src/main/assets/scripts/shBrushCpp.js rename to code-reader-kt/src/main/assets/scripts/shBrushCpp.js diff --git a/app/src/main/assets/scripts/shBrushCss.js b/code-reader-kt/src/main/assets/scripts/shBrushCss.js old mode 100755 new mode 100644 similarity index 100% rename from app/src/main/assets/scripts/shBrushCss.js rename to code-reader-kt/src/main/assets/scripts/shBrushCss.js diff --git a/app/src/main/assets/scripts/shBrushDelphi.js b/code-reader-kt/src/main/assets/scripts/shBrushDelphi.js old mode 100755 new mode 100644 similarity index 100% rename from app/src/main/assets/scripts/shBrushDelphi.js rename to code-reader-kt/src/main/assets/scripts/shBrushDelphi.js diff --git a/app/src/main/assets/scripts/shBrushDiff.js b/code-reader-kt/src/main/assets/scripts/shBrushDiff.js old mode 100755 new mode 100644 similarity index 100% rename from app/src/main/assets/scripts/shBrushDiff.js rename to code-reader-kt/src/main/assets/scripts/shBrushDiff.js diff --git a/app/src/main/assets/scripts/shBrushErlang.js b/code-reader-kt/src/main/assets/scripts/shBrushErlang.js old mode 100755 new mode 100644 similarity index 100% rename from app/src/main/assets/scripts/shBrushErlang.js rename to code-reader-kt/src/main/assets/scripts/shBrushErlang.js diff --git a/app/src/main/assets/scripts/shBrushGo.js b/code-reader-kt/src/main/assets/scripts/shBrushGo.js old mode 100755 new mode 100644 similarity index 100% rename from app/src/main/assets/scripts/shBrushGo.js rename to code-reader-kt/src/main/assets/scripts/shBrushGo.js diff --git a/app/src/main/assets/scripts/shBrushGradle.js b/code-reader-kt/src/main/assets/scripts/shBrushGradle.js old mode 100755 new mode 100644 similarity index 100% rename from app/src/main/assets/scripts/shBrushGradle.js rename to code-reader-kt/src/main/assets/scripts/shBrushGradle.js diff --git a/app/src/main/assets/scripts/shBrushGroovy.js b/code-reader-kt/src/main/assets/scripts/shBrushGroovy.js old mode 100755 new mode 100644 similarity index 100% rename from app/src/main/assets/scripts/shBrushGroovy.js rename to code-reader-kt/src/main/assets/scripts/shBrushGroovy.js diff --git a/app/src/main/assets/scripts/shBrushJScript.js b/code-reader-kt/src/main/assets/scripts/shBrushJScript.js old mode 100755 new mode 100644 similarity index 100% rename from app/src/main/assets/scripts/shBrushJScript.js rename to code-reader-kt/src/main/assets/scripts/shBrushJScript.js diff --git a/app/src/main/assets/scripts/shBrushJava.js b/code-reader-kt/src/main/assets/scripts/shBrushJava.js old mode 100755 new mode 100644 similarity index 100% rename from app/src/main/assets/scripts/shBrushJava.js rename to code-reader-kt/src/main/assets/scripts/shBrushJava.js diff --git a/app/src/main/assets/scripts/shBrushJavaFX.js b/code-reader-kt/src/main/assets/scripts/shBrushJavaFX.js old mode 100755 new mode 100644 similarity index 100% rename from app/src/main/assets/scripts/shBrushJavaFX.js rename to code-reader-kt/src/main/assets/scripts/shBrushJavaFX.js diff --git a/app/src/main/assets/scripts/shBrushJson.js b/code-reader-kt/src/main/assets/scripts/shBrushJson.js old mode 100755 new mode 100644 similarity index 100% rename from app/src/main/assets/scripts/shBrushJson.js rename to code-reader-kt/src/main/assets/scripts/shBrushJson.js diff --git a/app/src/main/assets/scripts/shBrushPerl.js b/code-reader-kt/src/main/assets/scripts/shBrushPerl.js old mode 100755 new mode 100644 similarity index 100% rename from app/src/main/assets/scripts/shBrushPerl.js rename to code-reader-kt/src/main/assets/scripts/shBrushPerl.js diff --git a/app/src/main/assets/scripts/shBrushPhp.js b/code-reader-kt/src/main/assets/scripts/shBrushPhp.js old mode 100755 new mode 100644 similarity index 100% rename from app/src/main/assets/scripts/shBrushPhp.js rename to code-reader-kt/src/main/assets/scripts/shBrushPhp.js diff --git a/app/src/main/assets/scripts/shBrushPlain.js b/code-reader-kt/src/main/assets/scripts/shBrushPlain.js old mode 100755 new mode 100644 similarity index 100% rename from app/src/main/assets/scripts/shBrushPlain.js rename to code-reader-kt/src/main/assets/scripts/shBrushPlain.js diff --git a/app/src/main/assets/scripts/shBrushPowerShell.js b/code-reader-kt/src/main/assets/scripts/shBrushPowerShell.js old mode 100755 new mode 100644 similarity index 100% rename from app/src/main/assets/scripts/shBrushPowerShell.js rename to code-reader-kt/src/main/assets/scripts/shBrushPowerShell.js diff --git a/app/src/main/assets/scripts/shBrushPro.js b/code-reader-kt/src/main/assets/scripts/shBrushPro.js old mode 100755 new mode 100644 similarity index 100% rename from app/src/main/assets/scripts/shBrushPro.js rename to code-reader-kt/src/main/assets/scripts/shBrushPro.js diff --git a/app/src/main/assets/scripts/shBrushProperties.js b/code-reader-kt/src/main/assets/scripts/shBrushProperties.js old mode 100755 new mode 100644 similarity index 100% rename from app/src/main/assets/scripts/shBrushProperties.js rename to code-reader-kt/src/main/assets/scripts/shBrushProperties.js diff --git a/app/src/main/assets/scripts/shBrushPython.js b/code-reader-kt/src/main/assets/scripts/shBrushPython.js old mode 100755 new mode 100644 similarity index 100% rename from app/src/main/assets/scripts/shBrushPython.js rename to code-reader-kt/src/main/assets/scripts/shBrushPython.js diff --git a/app/src/main/assets/scripts/shBrushRuby.js b/code-reader-kt/src/main/assets/scripts/shBrushRuby.js old mode 100755 new mode 100644 similarity index 100% rename from app/src/main/assets/scripts/shBrushRuby.js rename to code-reader-kt/src/main/assets/scripts/shBrushRuby.js diff --git a/app/src/main/assets/scripts/shBrushSass.js b/code-reader-kt/src/main/assets/scripts/shBrushSass.js old mode 100755 new mode 100644 similarity index 100% rename from app/src/main/assets/scripts/shBrushSass.js rename to code-reader-kt/src/main/assets/scripts/shBrushSass.js diff --git a/app/src/main/assets/scripts/shBrushScala.js b/code-reader-kt/src/main/assets/scripts/shBrushScala.js old mode 100755 new mode 100644 similarity index 100% rename from app/src/main/assets/scripts/shBrushScala.js rename to code-reader-kt/src/main/assets/scripts/shBrushScala.js diff --git a/app/src/main/assets/scripts/shBrushSql.js b/code-reader-kt/src/main/assets/scripts/shBrushSql.js old mode 100755 new mode 100644 similarity index 100% rename from app/src/main/assets/scripts/shBrushSql.js rename to code-reader-kt/src/main/assets/scripts/shBrushSql.js diff --git a/app/src/main/assets/scripts/shBrushSwift.js b/code-reader-kt/src/main/assets/scripts/shBrushSwift.js old mode 100755 new mode 100644 similarity index 100% rename from app/src/main/assets/scripts/shBrushSwift.js rename to code-reader-kt/src/main/assets/scripts/shBrushSwift.js diff --git a/app/src/main/assets/scripts/shBrushTxt.js b/code-reader-kt/src/main/assets/scripts/shBrushTxt.js old mode 100755 new mode 100644 similarity index 100% rename from app/src/main/assets/scripts/shBrushTxt.js rename to code-reader-kt/src/main/assets/scripts/shBrushTxt.js diff --git a/app/src/main/assets/scripts/shBrushVb.js b/code-reader-kt/src/main/assets/scripts/shBrushVb.js old mode 100755 new mode 100644 similarity index 100% rename from app/src/main/assets/scripts/shBrushVb.js rename to code-reader-kt/src/main/assets/scripts/shBrushVb.js diff --git a/app/src/main/assets/scripts/shBrushVerilog.js b/code-reader-kt/src/main/assets/scripts/shBrushVerilog.js old mode 100755 new mode 100644 similarity index 100% rename from app/src/main/assets/scripts/shBrushVerilog.js rename to code-reader-kt/src/main/assets/scripts/shBrushVerilog.js diff --git a/app/src/main/assets/scripts/shBrushXml.js b/code-reader-kt/src/main/assets/scripts/shBrushXml.js old mode 100755 new mode 100644 similarity index 100% rename from app/src/main/assets/scripts/shBrushXml.js rename to code-reader-kt/src/main/assets/scripts/shBrushXml.js diff --git a/app/src/main/assets/scripts/shCore.js b/code-reader-kt/src/main/assets/scripts/shCore.js old mode 100755 new mode 100644 similarity index 100% rename from app/src/main/assets/scripts/shCore.js rename to code-reader-kt/src/main/assets/scripts/shCore.js diff --git a/app/src/main/assets/scripts/shLegacy.js b/code-reader-kt/src/main/assets/scripts/shLegacy.js old mode 100755 new mode 100644 similarity index 100% rename from app/src/main/assets/scripts/shLegacy.js rename to code-reader-kt/src/main/assets/scripts/shLegacy.js diff --git a/app/src/main/assets/style.css b/code-reader-kt/src/main/assets/style.css old mode 100755 new mode 100644 similarity index 97% rename from app/src/main/assets/style.css rename to code-reader-kt/src/main/assets/style.css index 31caf44..736a1b4 --- a/app/src/main/assets/style.css +++ b/code-reader-kt/src/main/assets/style.css @@ -1,18 +1,18 @@ -body { border: 0px; } -body.code { padding: 0; margin: 10px 2px 0 0; } - -h3 { margin-top: 0; padding-top: 0; } -a { text-decoration: none; } - -.plug-header { height: 40px; line-height: 40px; vertical-align: middle; padding-left: 40px; display: block; } -.icon { background: left no-repeat; } - -.syntaxhighlighter { padding-top: 1px !important; margin-top: 0px !important; } -.syntaxhighlighter .lines.no-wrap { overflow: visible; } - -/* Background colours */ -.theme-Default { background-color: #fff; } - - -/* Alternate lines */ -.theme-Default .syntaxhighlighter .line.alt1 { background-color: #ffffff !important; } +body { border: 0px; } +body.code { padding: 0; margin: 10px 2px 0 0; } + +h3 { margin-top: 0; padding-top: 0; } +a { text-decoration: none; } + +.plug-header { height: 40px; line-height: 40px; vertical-align: middle; padding-left: 40px; display: block; } +.icon { background: left no-repeat; } + +.syntaxhighlighter { padding-top: 1px !important; margin-top: 0px !important; } +.syntaxhighlighter .lines.no-wrap { overflow: visible; } + +/* Background colours */ +.theme-Default { background-color: #fff; } + + +/* Alternate lines */ +.theme-Default .syntaxhighlighter .line.alt1 { background-color: #ffffff !important; } diff --git a/app/src/main/assets/style_menlo.css b/code-reader-kt/src/main/assets/style_menlo.css old mode 100755 new mode 100644 similarity index 96% rename from app/src/main/assets/style_menlo.css rename to code-reader-kt/src/main/assets/style_menlo.css index dcba04f..72e074f --- a/app/src/main/assets/style_menlo.css +++ b/code-reader-kt/src/main/assets/style_menlo.css @@ -1,9 +1,9 @@ -@font-face { - font-family: "Courier"; - src: url("file:///android_asset/fonts/Menlo-Regular.ttf") format("truetype"); -} - -.syntaxhighlighter, -.syntaxhighlighter div, -.syntaxhighlighter code, -.syntaxhighlighter span { font-family: "Courier" !important; } +@font-face { + font-family: "Courier"; + src: url("file:///android_asset/fonts/Menlo-Regular.ttf") format("truetype"); +} + +.syntaxhighlighter, +.syntaxhighlighter div, +.syntaxhighlighter code, +.syntaxhighlighter span { font-family: "Courier" !important; } diff --git a/app/src/main/assets/styles/shCore.css b/code-reader-kt/src/main/assets/styles/shCore.css old mode 100755 new mode 100644 similarity index 100% rename from app/src/main/assets/styles/shCore.css rename to code-reader-kt/src/main/assets/styles/shCore.css diff --git a/app/src/main/assets/styles/shCoreDefault.css b/code-reader-kt/src/main/assets/styles/shCoreDefault.css old mode 100755 new mode 100644 similarity index 100% rename from app/src/main/assets/styles/shCoreDefault.css rename to code-reader-kt/src/main/assets/styles/shCoreDefault.css diff --git a/app/src/main/assets/styles/shCoreNight.css b/code-reader-kt/src/main/assets/styles/shCoreNight.css old mode 100755 new mode 100644 similarity index 100% rename from app/src/main/assets/styles/shCoreNight.css rename to code-reader-kt/src/main/assets/styles/shCoreNight.css diff --git a/app/src/main/assets/styles/shThemeDefault.css b/code-reader-kt/src/main/assets/styles/shThemeDefault.css old mode 100755 new mode 100644 similarity index 100% rename from app/src/main/assets/styles/shThemeDefault.css rename to code-reader-kt/src/main/assets/styles/shThemeDefault.css diff --git a/app/src/main/assets/styles/shThemeNight.css b/code-reader-kt/src/main/assets/styles/shThemeNight.css old mode 100755 new mode 100644 similarity index 100% rename from app/src/main/assets/styles/shThemeNight.css rename to code-reader-kt/src/main/assets/styles/shThemeNight.css diff --git a/code-reader-kt/src/main/java/com/loopeer/codereaderkt/CodeReaderApplication.kt b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/CodeReaderApplication.kt new file mode 100644 index 0000000..8b6b455 --- /dev/null +++ b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/CodeReaderApplication.kt @@ -0,0 +1,31 @@ +package com.loopeer.codereaderkt + +import android.app.Application +import android.content.Context +import android.support.v7.app.AppCompatDelegate +import com.facebook.stetho.Stetho +import com.loopeer.codereaderkt.utils.ThemeUtils + + +class CodeReaderApplication : Application() { + + override fun onCreate() { + super.onCreate() + instance = this + appContext = applicationContext + Stetho.initialize( + Stetho.newInitializerBuilder(this) + .enableDumpapp(Stetho.defaultDumperPluginsProvider(this)) + .enableWebKitInspector(Stetho.defaultInspectorModulesProvider(this)) + .build()) + AppCompatDelegate.setDefaultNightMode(ThemeUtils.getCurrentNightMode(this)) + } + + companion object { + lateinit var instance: CodeReaderApplication + private set + lateinit var appContext: Context + private set + } + +} \ No newline at end of file diff --git a/code-reader-kt/src/main/java/com/loopeer/codereaderkt/Navigator.kt b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/Navigator.kt new file mode 100644 index 0000000..872d98f --- /dev/null +++ b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/Navigator.kt @@ -0,0 +1,126 @@ +package com.loopeer.codereaderkt + +import android.app.DownloadManager +import android.content.Context +import android.content.Intent +import android.net.Uri +import android.text.Html +import android.widget.Toast +import com.loopeer.codereaderkt.db.CoReaderDbHelper +import com.loopeer.codereaderkt.model.Repo +import com.loopeer.codereaderkt.sync.DownloadRepoService +import com.loopeer.codereaderkt.ui.activity.* + + +open class Navigator { + companion object { + //静态变量这么写!!! + val EXTRA_REPO = "extra_repo" + val EXTRA_ID = "extra_id" + val EXTRA_DOWNLOAD_SERVICE_TYPE = "extra_download_service_type" + val EXTRA_DIRETORY_ROOT = "extra_diretory_root" + val EXTRA_DIRETORY_ROOT_NODE_INSTANCE = "extra_diretory_root_node_instance" + val EXTRA_DIRETORY_SELECTING = "extra_diretory_selecting" + val EXTRA_WEB_URL = "extra_web_url" + val EXTRA_HTML_STRING = "extra_html_string" + } + + + fun startMainActivity(context: Context) { + val intent = Intent(context, MainActivity::class.java) + intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP + context.startActivity(intent) + } + + fun startLoginActivity(context: Context) { + val intent = Intent(context, LoginActivity::class.java) + context.startActivity(intent) + } + + fun startSettingActivity(context: Context) { + val intent = Intent(context, SettingActivity::class.java) + context.startActivity(intent) + } + + fun startAddRepoActivity(context: Context) { + val intent = Intent(context, AddRepoActivity::class.java) + context.startActivity(intent) + } + + fun startAboutActivity(context: Context) { + val intent = Intent(context, AboutActivity::class.java) + context.startActivity(intent) + } + + fun startWebActivity(context: Context, url: String) { + val intent = Intent(context, SimpleWebActivity::class.java) + intent.putExtra(EXTRA_WEB_URL, url) + context.startActivity(intent) + } + + fun startComposeEmail(context: Context, addresses: Array, subject: String, content: String) { + val intent = Intent(Intent.ACTION_SENDTO, Uri.parse("mailto:")) + intent.putExtra(Intent.EXTRA_SUBJECT, subject) + intent.putExtra(Intent.EXTRA_EMAIL, addresses) + intent.putExtra(Intent.EXTRA_TEXT, Html.fromHtml(content)) + if (intent.resolveActivity(context.packageManager) != null) { + context.startActivity(intent) + } else { + Toast.makeText(context, R.string.about_email_app_not_have, Toast.LENGTH_SHORT).show() + } + } + + fun startDownloadRepoService(context: Context, repo: Repo) { + val intent = Intent(context, DownloadRepoService::class.java) + intent.putExtra(EXTRA_REPO, repo) + intent.putExtra(EXTRA_DOWNLOAD_SERVICE_TYPE, DownloadRepoService.DOWNLOAD_REPO) + context.startService(intent) + } + + fun startDownloadRepoService(context: Context, type: Int) { + val intent = Intent(context, DownloadRepoService::class.java) + intent.putExtra(EXTRA_DOWNLOAD_SERVICE_TYPE, type) + context.startService(intent) + } + + fun startDownloadRepoServiceRemove(context: Context, downloadId: Long) { + val intent = Intent(context, DownloadRepoService::class.java) + intent.putExtra(EXTRA_DOWNLOAD_SERVICE_TYPE, DownloadRepoService.DOWNLOAD_REMOVE_DOWNLOAD) + intent.putExtra(DownloadManager.EXTRA_DOWNLOAD_ID, downloadId) + context.startService(intent) + } + + fun startDownloadNewRepoService(context: Context, repo: Repo) { + val sameRepo = CoReaderDbHelper.getInstance(context).readSameRepo(repo) + val repoId: Long + if (sameRepo != null) { + repoId = java.lang.Long.parseLong(sameRepo.id) + } else { + repoId = CoReaderDbHelper.getInstance(context).insertRepo(repo) + } + repo.id = repoId.toString() + Navigator().startDownloadRepoService(context, repo) + } + + fun startSearchActivity(context: Context) { + val intent = Intent(context, SearchActivity::class.java) + context.startActivity(intent) + } + + fun startOutWebActivity(context: Context, url: String) { + val intent = Intent() + intent.action = Intent.ACTION_VIEW + val content_uri = Uri.parse(url) + intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK + intent.data = content_uri + context.startActivity(intent) + } + + fun startCodeReadActivity(context: Context, repo: Repo?) { + val intent = Intent(context, CodeReadActivity::class.java) + intent.putExtra(EXTRA_REPO, repo) + context.startActivity(intent) + } + + +} \ No newline at end of file diff --git a/code-reader-kt/src/main/java/com/loopeer/codereaderkt/api/ApiService.kt b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/api/ApiService.kt new file mode 100644 index 0000000..00a2309 --- /dev/null +++ b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/api/ApiService.kt @@ -0,0 +1,80 @@ +package com.loopeer.codereaderkt.api + +import android.app.Application +import com.loopeer.codereaderkt.BuildConfig +import com.loopeer.codereaderkt.CodeReaderApplication +import okhttp3.Cache +import okhttp3.OkHttpClient +import retrofit2.Retrofit +import retrofit2.adapter.rxjava.RxJavaCallAdapterFactory +import retrofit2.converter.gson.GsonConverterFactory +import java.io.File +import java.util.* +import java.util.concurrent.TimeUnit + + +open class ApiService { + + private var mRetrofit: Retrofit? = null + + + private fun getClient(): OkHttpClient { + return createOkHttpClient(CodeReaderApplication.instance) + } + + val retrofit: Retrofit + get() { + if (mRetrofit == null) { + try { + mRetrofit = Retrofit.Builder() + .client(getClient()) + .addConverterFactory(GsonConverterFactory.create()) + .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) + .baseUrl(API_URL) + .build() + } catch (e: NullPointerException) { + throw MissingResourceException("Define your endpoint in api_url string resource.", javaClass.name, "api_url") + } + + } + + return this.mRetrofit!! + } + + companion object { + + val API_URL = "https://api.github.com/" + + private var sInstance: ApiService? = null + + val instance: ApiService + @Synchronized get() { + if (sInstance == null) { + sInstance = ApiService() + } + return sInstance!! + } + + private val DISK_CACHE_SIZE = 50 * 1024 * 1024 // 50MB + + internal fun createOkHttpClient(app: Application): OkHttpClient { + val httpClient = OkHttpClient.Builder() + + if (BuildConfig.DEBUG) { + val loggingInterceptor = HttpJsonLoggingInterceptor() + loggingInterceptor.level = HttpJsonLoggingInterceptor.Level.BODY + httpClient.addInterceptor(loggingInterceptor) + } + + httpClient.connectTimeout(1, TimeUnit.HOURS) // connect timeout + httpClient.readTimeout(1, TimeUnit.HOURS) + val cacheDir = File(app.cacheDir, "http") + val cache = Cache(cacheDir, DISK_CACHE_SIZE.toLong()) + httpClient.cache(cache) + return httpClient.build() + } + + fun create(service: Class): T = instance.retrofit.create(service) + } + +} \ No newline at end of file diff --git a/code-reader-kt/src/main/java/com/loopeer/codereaderkt/api/BaseListResponse.kt b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/api/BaseListResponse.kt new file mode 100644 index 0000000..4ddb231 --- /dev/null +++ b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/api/BaseListResponse.kt @@ -0,0 +1,15 @@ +package com.loopeer.codereaderkt.api + +import com.google.gson.annotations.SerializedName + + +class BaseListResponse { + + @SerializedName("total_count") + var totalCount: Int = 0 + @SerializedName("incomplete_results") + var incompleteResults: Boolean = false + @SerializedName("items") + var items: List? = null + +} \ No newline at end of file diff --git a/code-reader-kt/src/main/java/com/loopeer/codereaderkt/api/HttpJsonLoggingInterceptor.kt b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/api/HttpJsonLoggingInterceptor.kt new file mode 100644 index 0000000..8869286 --- /dev/null +++ b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/api/HttpJsonLoggingInterceptor.kt @@ -0,0 +1,300 @@ +package com.loopeer.codereaderkt.api + +import okhttp3.Headers +import okhttp3.Interceptor +import okhttp3.Protocol +import okhttp3.Response +import okhttp3.internal.http.HttpHeaders +import okhttp3.internal.platform.Platform +import okhttp3.internal.platform.Platform.INFO +import okio.Buffer +import org.json.JSONException +import org.json.JSONObject +import java.io.EOFException +import java.io.IOException +import java.nio.charset.Charset +import java.nio.charset.UnsupportedCharsetException +import java.util.concurrent.TimeUnit + + +class HttpJsonLoggingInterceptor @JvmOverloads constructor(private val logger: Logger = Logger.DEFAULT) : Interceptor { + + enum class Level { + /** + * No logs. + */ + NONE, + /** + * Logs request and response lines. + * + * + * + * Example: + *
`--> POST /greeting http/1.1 (3-byte body)
+         *
+         * <-- 200 OK (22ms, 6-byte body)
+        `
* + */ + BASIC, + /** + * Logs request and response lines and their respective headers. + * + * + * + * Example: + *
`--> POST /greeting http/1.1
+         * Host: example.com
+         * Content-Type: plain/text
+         * Content-Length: 3
+         * --> END POST
+         *
+         * <-- 200 OK (22ms)
+         * Content-Type: plain/text
+         * Content-Length: 6
+         * <-- END HTTP
+        `
* + */ + HEADERS, + /** + * Logs request and response lines and their respective headers and bodies (if present). + * + * + * + * Example: + *
`--> POST /greeting http/1.1
+         * Host: example.com
+         * Content-Type: plain/text
+         * Content-Length: 3
+         *
+         * Hi?
+         * --> END POST
+         *
+         * <-- 200 OK (22ms)
+         * Content-Type: plain/text
+         * Content-Length: 6
+         *
+         * Hello!
+         * <-- END HTTP
+        `
* + */ + BODY + } + + interface Logger { + fun log(message: String) + + companion object { + + /** + * A [Logger] defaults output appropriate for the current platform. + */ + val DEFAULT: Logger = object : Logger { + override fun log(message: String) { + Platform.get().log(INFO, message, null) + } + } + } + } + + @Volatile internal var level = Level.NONE + + /** + * Change the level at which this interceptor logs. + */ + fun setLevel(level: Level?): HttpJsonLoggingInterceptor { + if (level == null) throw NullPointerException("level == null. Use Level.NONE instead.") + this.level = level + return this + } + + fun getLevel(): Level { + return level + } + + @Throws(IOException::class) + override fun intercept(chain: Interceptor.Chain): Response { + val level = this.level + + val request = chain.request() + if (level == Level.NONE) { + return chain.proceed(request) + } + + val logBody = level == Level.BODY + val logHeaders = logBody || level == Level.HEADERS + + val requestBody = request.body() + val hasRequestBody = requestBody != null + + val connection = chain.connection() + val protocol = if (connection != null) connection.protocol() else Protocol.HTTP_1_1 + var requestStartMessage = "--> " + request.method() + ' ' + request.url() + ' ' + protocol + if (!logHeaders && hasRequestBody) { + requestStartMessage += " (" + requestBody!!.contentLength() + "-byte body)" + } + logger.log(requestStartMessage) + + if (logHeaders) { + if (hasRequestBody) { + // Request body headers are only present when installed as a network interceptor. Force + // them to be included (when available) so there values are known. + if (requestBody!!.contentType() != null) { + logger.log("Content-Type: " + requestBody.contentType()) + } + if (requestBody.contentLength() != -1L) { + logger.log("Content-Length: " + requestBody.contentLength()) + } + } + + val headers = request.headers() + var i = 0 + val count = headers.size() + while (i < count) { + val name = headers.name(i) + // Skip headers from the request body as they are explicitly logged above. + if (!"Content-Type".equals(name, ignoreCase = true) && !"Content-Length".equals(name, ignoreCase = true)) { + logger.log(name + ": " + headers.value(i)) + } + i++ + } + + if (!logBody || !hasRequestBody) { + logger.log("--> END " + request.method()) + } else if (bodyEncoded(request.headers())) { + logger.log("--> END " + request.method() + " (encoded body omitted)") + } else { + val buffer = Buffer() + requestBody!!.writeTo(buffer) + + var charset = UTF8 + val contentType = requestBody.contentType() + if (contentType != null) { + charset = contentType.charset(UTF8) + } + + logger.log("") + if (isPlaintext(buffer)) { + logger.log(buffer.readString(charset)) + logger.log("--> END " + request.method() + + " (" + requestBody.contentLength() + "-byte body)") + } else { + logger.log("--> END " + request.method() + " (binary " + + requestBody.contentLength() + "-byte body omitted)") + } + } + } + + val startNs = System.nanoTime() + val response: Response + try { + response = chain.proceed(request) + } catch (e: Exception) { + logger.log("<-- HTTP FAILED: " + e) + throw e + } + + val tookMs = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startNs) + + val responseBody = response.body() + val contentLength = responseBody.contentLength() + val bodySize = if (contentLength != -1L) contentLength.toString() + "-byte" else "unknown-length" + logger.log("<-- " + response.code() + ' ' + response.message() + ' ' + + response.request().url() + " (" + tookMs + "ms" + (if (!logHeaders) + ", " + + bodySize + " body" + else + "") + ')') + + if (logHeaders) { + val headers = response.headers() + var i = 0 + val count = headers.size() + while (i < count) { + logger.log(headers.name(i) + ": " + headers.value(i)) + i++ + } + + if (!logBody || !HttpHeaders.hasBody(response)) { + logger.log("<-- END HTTP") + } else if (bodyEncoded(response.headers())) { + logger.log("<-- END HTTP (encoded body omitted)") + } else { + val source = responseBody.source() + source.request(java.lang.Long.MAX_VALUE) // Buffer the entire body. + val buffer = source.buffer() + + var charset = UTF8 + val contentType = responseBody.contentType() + if (contentType != null) { + try { + charset = contentType.charset(UTF8) + } catch (e: UnsupportedCharsetException) { + logger.log("") + logger.log("Couldn't decode the response body; charset is likely malformed.") + logger.log("<-- END HTTP") + + return response + } + + } + + if (!isPlaintext(buffer)) { + logger.log("") + logger.log("<-- END HTTP (binary " + buffer.size() + "-byte body omitted)") + return response + } + + if (contentLength != 0L) { + logger.log("") + val logText = buffer.clone().readString(charset) + try { + val logJson = JSONObject(logText) + logger.log(logJson.toString(4)) + } catch (e: JSONException) { + e.printStackTrace() + logger.log(logText) + } + + } + + logger.log("<-- END HTTP (" + buffer.size() + "-byte body)") + } + } + + return response + } + + private fun bodyEncoded(headers: Headers): Boolean { + val contentEncoding = headers.get("Content-Encoding") + return contentEncoding != null && !contentEncoding.equals("identity", ignoreCase = true) + } + + companion object { + private val UTF8 = Charset.forName("UTF-8") + + /** + * Returns true if the body in question probably contains human readable text. Uses a small sample + * of code points to detect unicode control characters commonly used in binary file signatures. + */ + internal fun isPlaintext(buffer: Buffer): Boolean { + try { + val prefix = Buffer() + val byteCount = if (buffer.size() < 64) buffer.size() else 64 + buffer.copyTo(prefix, 0, byteCount) + for (i in 0..15) { + if (prefix.exhausted()) { + break + } + val codePoint = prefix.readUtf8CodePoint() + if (Character.isISOControl(codePoint) && !Character.isWhitespace(codePoint)) { + return false + } + } + return true + } catch (e: EOFException) { + return false // Truncated UTF-8 sequence. + } + + } + } +} \ No newline at end of file diff --git a/code-reader-kt/src/main/java/com/loopeer/codereaderkt/api/ServiceFactory.kt b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/api/ServiceFactory.kt new file mode 100644 index 0000000..67f1768 --- /dev/null +++ b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/api/ServiceFactory.kt @@ -0,0 +1,12 @@ +package com.loopeer.codereaderkt.api + +import com.loopeer.codereaderkt.api.service.GithubService + + +class ServiceFactory { + + @Synchronized fun getGithubService(): GithubService = + ServiceUtil().getApiService().retrofit.create(GithubService::class.java) + + //这个注释符号代表java中的static相似 +} \ No newline at end of file diff --git a/code-reader-kt/src/main/java/com/loopeer/codereaderkt/api/ServiceUtil.kt b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/api/ServiceUtil.kt new file mode 100644 index 0000000..958849e --- /dev/null +++ b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/api/ServiceUtil.kt @@ -0,0 +1,12 @@ +package com.loopeer.codereaderkt.api + + +class ServiceUtil { + private var sApiService: ApiService ?= null + fun getApiService(): ApiService { + if (sApiService == null) { + sApiService = ApiService() + } + return sApiService as ApiService + } +} \ No newline at end of file diff --git a/code-reader-kt/src/main/java/com/loopeer/codereaderkt/api/service/GithubService.kt b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/api/service/GithubService.kt new file mode 100644 index 0000000..fda3bfe --- /dev/null +++ b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/api/service/GithubService.kt @@ -0,0 +1,38 @@ +package com.loopeer.codereaderkt.api.service + +import com.loopeer.codereaderkt.api.BaseListResponse +import com.loopeer.codereaderkt.model.Empty +import com.loopeer.codereaderkt.model.Repository +import com.loopeer.codereaderkt.model.Token +import okhttp3.ResponseBody +import retrofit2.Response +import retrofit2.http.* +import rx.Observable + + +interface GithubService { + + @Streaming + @GET + fun downloadRepo(@Url fileUrl: String): Observable + + @GET("/search/repositories") + fun repositories( + @Query("q") keyword: String, + @Query("sort") sort: String, + @Query("order") order: String, + @Query("page") page: Int, + @Query("per_page") pageSize: Int + ): Observable>> + + // Api about token + @POST("authorizations") + fun createToken(@Body token: Token, @Header("Authorization") authorization: String): Observable> + + @GET("authorizations") + fun listToken(@Header("Authorization") authorization: String): Observable>> + + @DELETE("authorizations/{id}") + fun removeToken(@Header("Authorization") authorization: String, @Path("id") id: String): Observable> + +} diff --git a/code-reader-kt/src/main/java/com/loopeer/codereaderkt/db/CoReaderDbHelper.kt b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/db/CoReaderDbHelper.kt new file mode 100644 index 0000000..1857c0b --- /dev/null +++ b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/db/CoReaderDbHelper.kt @@ -0,0 +1,149 @@ +package com.loopeer.codereaderkt.db + +import android.content.Context +import android.database.Cursor +import android.database.sqlite.SQLiteDatabase +import android.database.sqlite.SQLiteOpenHelper +import com.loopeer.codereaderkt.CodeReaderApplication +import com.loopeer.codereaderkt.model.Repo +import java.util.* + +//从Trending中下载多个时会发生数组越界错误 +class CoReaderDbHelper private constructor(context: Context) + : SQLiteOpenHelper(CodeReaderApplication.appContext, DATABASE_NAME, null, DATABASE_VERSION) { + + override fun onCreate(db: SQLiteDatabase) { + db.execSQL(DbRepoModel.CREATE_TABLE) + } + + override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) { + + if (oldVersion != DATABASE_VERSION) { + db.execSQL("DROP TABLE IF EXISTS " + DbRepoModel.TABLE_NAME) + onCreate(db) + } + } + + fun deleteAllTable() { + val db = writableDatabase + db.delete(DbRepoModel.TABLE_NAME, null, null) + db.close() + } + + fun insertRepo(repo: Repo): Long { + val same = readSameRepo(repo) + if (same != null) return java.lang.Long.valueOf(same.id)!! + if (repo.lastModify == 0L) repo.lastModify = System.currentTimeMillis() + val db = writableDatabase + return db.insert(DbRepoModel.TABLE_NAME, null, DbRepo.FACTORY.marshal() + .name(repo.name) + .absolute_path(repo.absolutePath) + .last_modify(repo.lastModify) + .net_url(repo.netDownloadUrl) + .is_folder(repo.isFolder) + .download_id(repo.downloadId) + .factor(repo.factor) + .is_unzip(repo.isUnzip) + .asContentValues()) + } + + private fun haveSameRepo(repo: Repo): Boolean { + val repo1 = readSameRepo(repo) + return if (repo1 != null) true else false + } + + fun readSameRepo(repo: Repo): Repo? { + val db = readableDatabase + var result: Repo? = null + val cursor = db.rawQuery( + DbRepo.CHECK_SAME_REPO, + arrayOf(repo.name.toString(), repo.absolutePath.toString())) + if (cursor.moveToFirst()) { + result = parseRepo(cursor) + } + return result + } + + private fun parseRepo(cursor: Cursor): Repo { + val dbRepo = DbRepo.FOR_TEAM_MAPPER.map(cursor) + val repo = Repo() + repo.id = dbRepo._id().toString() + repo.name = dbRepo.name() + repo.absolutePath = dbRepo.absolute_path() + repo.netDownloadUrl = dbRepo.net_url() + repo.isFolder = dbRepo.is_folder!! + repo.lastModify = dbRepo.last_modify()!! + repo.downloadId = dbRepo.download_id()!! + repo.factor = dbRepo.factor()!! + repo.isUnzip = dbRepo.is_unzip!! + return repo + } + + fun readRepos(): List { + val db = readableDatabase//打开database引起的异常 + val repos = ArrayList() + val cursor = db.rawQuery(DbRepo.SELECT_ALL, null) + if (cursor != null && cursor.count > 0) { + while (cursor.moveToNext()) { + val repo = parseRepo(cursor) + if (repo != null) { + repos.add(repo) + } + } + } + return repos + } + + fun updateRepoLastModify(primaryKey: Long, lastModify: Long) { + val db = writableDatabase + db.execSQL(DbRepoModel.UPDATE_LAST_MODIFY, arrayOf(lastModify.toString(), primaryKey.toString())) + } + + fun updateRepoDownloadId(downloadId: Long, repoId: String?) { + val db = writableDatabase + db.execSQL(DbRepoModel.UPDATE_DOWNLOAD_ID, arrayOf(downloadId.toString(), repoId.toString())) + } + + fun resetRepoDownloadId(downloadId: Long) { + val db = writableDatabase + db.execSQL(DbRepoModel.RESET_DOWNLOAD_ID, arrayOf(downloadId.toString())) + } + + fun updateRepoDownloadProgress(downloadId: Long, factor: Float) { + val db = writableDatabase + db.execSQL(DbRepoModel.UPDATE_DOWNLOAD_PROGRESS, arrayOf(factor.toString(), downloadId.toString())) + } + + fun updateRepoUnzipProgress(downloadId: Long, factor: Float, isUnzip: Boolean) { + val db = writableDatabase + db.execSQL(DbRepoModel.UPDATE_UNZIP_PROGRESS, arrayOf(factor.toString(), (if (isUnzip) 1 else 0).toString(), downloadId.toString())) + } + + fun deleteRepo(id: Long) { + val db = writableDatabase + db.execSQL(DbRepoModel.DELETE_REPO, arrayOf(id.toString())) + } + + companion object { + private val TAG = "CoReaderDbHelper" + + private val DATABASE_NAME = "coreader.db" + private val DATABASE_VERSION = 1 + + @Volatile private var sInstance: CoReaderDbHelper? = null + + fun getInstance(context: Context): CoReaderDbHelper { + var inst = sInstance + if (inst == null) { + synchronized(CoReaderDbHelper::class.java) { + inst = sInstance + if (inst == null) { + inst = CoReaderDbHelper(context) + sInstance = inst + } + } + } + return inst!! + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/loopeer/codereader/coreader/db/DbRepo.java b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/db/DbRepo.java similarity index 92% rename from app/src/main/java/com/loopeer/codereader/coreader/db/DbRepo.java rename to code-reader-kt/src/main/java/com/loopeer/codereaderkt/db/DbRepo.java index 5b6b9c1..1ad990a 100644 --- a/app/src/main/java/com/loopeer/codereader/coreader/db/DbRepo.java +++ b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/db/DbRepo.java @@ -1,4 +1,4 @@ -package com.loopeer.codereader.coreader.db; +package com.loopeer.codereaderkt.db; import com.google.auto.value.AutoValue; import com.squareup.sqldelight.RowMapper; diff --git a/code-reader-kt/src/main/java/com/loopeer/codereaderkt/event/DownloadFailDeleteEvent.kt b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/event/DownloadFailDeleteEvent.kt new file mode 100644 index 0000000..70cc808 --- /dev/null +++ b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/event/DownloadFailDeleteEvent.kt @@ -0,0 +1,6 @@ +package com.loopeer.codereaderkt.event + +import com.loopeer.codereaderkt.model.Repo + + +class DownloadFailDeleteEvent(var deleteRepo: Repo) diff --git a/code-reader-kt/src/main/java/com/loopeer/codereaderkt/event/DownloadProgressEvent.kt b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/event/DownloadProgressEvent.kt new file mode 100644 index 0000000..3514d63 --- /dev/null +++ b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/event/DownloadProgressEvent.kt @@ -0,0 +1,25 @@ +package com.loopeer.codereaderkt.event + + +class DownloadProgressEvent { + + public var repoId: String = "" + public var downloadId: Long = 0 + public var factor: Float = 0.0f + public var isUnzip: Boolean = false + + constructor(downloadId: Long, isUnzip: Boolean) { + this.downloadId = downloadId + this.factor = 1f + this.isUnzip = isUnzip + } + + constructor(repoId: String, downloadId: Long, factor: Float, isUnzip: Boolean) { + this.repoId = repoId + this.downloadId = downloadId + this.factor = factor + this.isUnzip = isUnzip + } + + +} diff --git a/code-reader-kt/src/main/java/com/loopeer/codereaderkt/event/DownloadRepoMessageEvent.kt b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/event/DownloadRepoMessageEvent.kt new file mode 100644 index 0000000..381d3b2 --- /dev/null +++ b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/event/DownloadRepoMessageEvent.kt @@ -0,0 +1,4 @@ +package com.loopeer.codereaderkt.event + + +class DownloadRepoMessageEvent(var message: String) diff --git a/code-reader-kt/src/main/java/com/loopeer/codereaderkt/event/ThemeRecreateEvent.kt b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/event/ThemeRecreateEvent.kt new file mode 100644 index 0000000..03cabf0 --- /dev/null +++ b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/event/ThemeRecreateEvent.kt @@ -0,0 +1,4 @@ +package com.loopeer.codereaderkt.event + + +class ThemeRecreateEvent \ No newline at end of file diff --git a/code-reader-kt/src/main/java/com/loopeer/codereaderkt/model/BaseModel.kt b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/model/BaseModel.kt new file mode 100644 index 0000000..f4fc40c --- /dev/null +++ b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/model/BaseModel.kt @@ -0,0 +1,8 @@ +package com.loopeer.codereaderkt.model + +import java.io.Serializable + + +open class BaseModel : Serializable { + var id: String? = null +} \ No newline at end of file diff --git a/code-reader-kt/src/main/java/com/loopeer/codereaderkt/model/DirectoryNode.kt b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/model/DirectoryNode.kt new file mode 100644 index 0000000..b8d479f --- /dev/null +++ b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/model/DirectoryNode.kt @@ -0,0 +1,38 @@ +package com.loopeer.codereaderkt.model + + +class DirectoryNode: BaseModel { + + lateinit var name: String + var pathNodes: List?=null + var isDirectory: Boolean = false + var openChild: Boolean = false + var depth: Int = 0 + var displayName: String? = null + var absolutePath: String? = null + + constructor(name: String) { + this.name = name + } + + constructor(pathNodes: List, name: String) { + this.pathNodes = pathNodes + this.name = name + } + + operator fun compareTo(o: Any): Int { + if (o !is DirectoryNode) { + return 1 + } + if (o.isDirectory && !isDirectory) { + return 1 + } + if (!o.isDirectory && isDirectory) { + return -1 + } + return name.compareTo(o.name) + } + + constructor() + +} \ No newline at end of file diff --git a/code-reader-kt/src/main/java/com/loopeer/codereaderkt/model/Empty.kt b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/model/Empty.kt new file mode 100644 index 0000000..5e3b92f --- /dev/null +++ b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/model/Empty.kt @@ -0,0 +1,4 @@ +package com.loopeer.codereaderkt.model + + +class Empty \ No newline at end of file diff --git a/code-reader-kt/src/main/java/com/loopeer/codereaderkt/model/MainHeadlerItem.kt b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/model/MainHeadlerItem.kt new file mode 100644 index 0000000..49dd6ef --- /dev/null +++ b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/model/MainHeadlerItem.kt @@ -0,0 +1,4 @@ +package com.loopeer.codereaderkt.model + + +class MainHeaderItem(var icon: Int, var name: Int, var link: String) \ No newline at end of file diff --git a/code-reader-kt/src/main/java/com/loopeer/codereaderkt/model/Repo.kt b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/model/Repo.kt new file mode 100644 index 0000000..c9a68f4 --- /dev/null +++ b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/model/Repo.kt @@ -0,0 +1,87 @@ +package com.loopeer.codereaderkt.model + +import android.text.TextUtils +import com.loopeer.directorychooser.FileNod + + +class Repo() : BaseModel() { + + var name: String? = null + var lastModify: Long = 0 + var absolutePath: String? = null + var netDownloadUrl: String? = null + var isFolder: Boolean = false + var downloadId: Long = 0 + var factor: Float = 0.toFloat() + var isUnzip: Boolean = false + + init { + + } + + constructor(name: String?, absolutePath: String?, netDownloadUrl: String?, isFolder: Boolean, downloadId: Long) : this() { + this.name = name + this.absolutePath = absolutePath + this.netDownloadUrl = netDownloadUrl + this.isFolder = isFolder + this.downloadId = downloadId + } + + fun isDownloading(): Boolean = downloadId > 0 + + fun parse(node: FileNod): Repo { + val result = Repo() + result.name = node.name + result.absolutePath = node.absolutePath + result.isFolder = node.isFolder + return result + } + + fun toDirectoryNode(): DirectoryNode { + val node = DirectoryNode() + node.name = name!! + node.absolutePath = absolutePath + node.isDirectory = isFolder + return node + } + + fun isNetRepo(): Boolean = !TextUtils.isEmpty(netDownloadUrl) + + fun isLocalRepo(): Boolean = !TextUtils.isEmpty(absolutePath) + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other == null || javaClass != other.javaClass) return false + + val repo = other as Repo? + + if (if (id != null) id != repo!!.id else repo!!.id != null) return false + if (if (name != null) name != repo.name else repo.name != null) return false + if (if (absolutePath != null) absolutePath != repo.absolutePath else repo.absolutePath != null) + return false + return if (netDownloadUrl != null) netDownloadUrl == repo.netDownloadUrl else repo.netDownloadUrl == null + + } + + override fun hashCode(): Int { + var result = if (name != null) name!!.hashCode() else 0 + result = 31 * result + if (id != null) id!!.hashCode() else 0 + result = 31 * result + if (absolutePath != null) absolutePath!!.hashCode() else 0 + result = 31 * result + if (netDownloadUrl != null) netDownloadUrl!!.hashCode() else 0 + return result + } + + override fun toString(): String { + return "Repo{" + + "id='" + id + '\'' + + "name='" + name + '\'' + + ", lastModify=" + lastModify + + ", absolutePath='" + absolutePath + '\'' + + ", netDownloadUrl='" + netDownloadUrl + '\'' + + ", isFolder=" + isFolder + + ", downloadId=" + downloadId + + ", factor=" + factor + + ", isUnzip=" + isUnzip + + '}' + } +} \ No newline at end of file diff --git a/code-reader-kt/src/main/java/com/loopeer/codereaderkt/model/Repository.kt b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/model/Repository.kt new file mode 100644 index 0000000..82a59dd --- /dev/null +++ b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/model/Repository.kt @@ -0,0 +1,68 @@ +package com.loopeer.codereaderkt.model + +import com.google.gson.annotations.SerializedName + + +class Repository : BaseModel() { + + @SerializedName("name") + var name: String? = null + @SerializedName("full_name") + var fullName: String? = null + @SerializedName("owner") + var owner: Owner? = null + @SerializedName("private") + var privateX: Boolean = false + @SerializedName("html_url") + var htmlUrl: String? = null + @SerializedName("description") + var description: String? = null + @SerializedName("fork") + var fork: Boolean = false + @SerializedName("url") + var url: String? = null + @SerializedName("created_at") + var createdAt: String? = null + @SerializedName("updated_at") + var updatedAt: String? = null + @SerializedName("pushed_at") + var pushedAt: String? = null + @SerializedName("homepage") + var homepage: String? = null + @SerializedName("size") + var size: Int = 0 + @SerializedName("stargazers_count") + var stargazersCount: Int = 0 + @SerializedName("watchers_count") + var watchersCount: Int = 0 + @SerializedName("language") + var language: String? = null + @SerializedName("forks_count") + var forksCount: Int = 0 + @SerializedName("open_issues_count") + var openIssuesCount: Int = 0 + @SerializedName("master_branch") + var masterBranch: String? = null + @SerializedName("default_branch") + var defaultBranch: String? = null + @SerializedName("score") + var score: Double = 0.toDouble() + + class Owner { + @SerializedName("login") + var login: String? = null + @SerializedName("id") + var id: Int = 0 + @SerializedName("avatar_url") + var avatarUrl: String? = null + @SerializedName("gravatar_id") + var gravatarId: String? = null + @SerializedName("url") + var url: String? = null + @SerializedName("received_events_url") + var receivedEventsUrl: String? = null + @SerializedName("type") + var type: String? = null + } + +} \ No newline at end of file diff --git a/code-reader-kt/src/main/java/com/loopeer/codereaderkt/model/TargetFile.kt b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/model/TargetFile.kt new file mode 100644 index 0000000..1adc008 --- /dev/null +++ b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/model/TargetFile.kt @@ -0,0 +1,7 @@ +package com.loopeer.codereaderkt.model + + +class TargetFile : BaseModel() { + + var path: String? = null +} \ No newline at end of file diff --git a/code-reader-kt/src/main/java/com/loopeer/codereaderkt/model/Token.kt b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/model/Token.kt new file mode 100644 index 0000000..6c62eaa --- /dev/null +++ b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/model/Token.kt @@ -0,0 +1,42 @@ +package com.loopeer.codereaderkt.model + + +class Token { + var id: Int = 0 + var url: String? = null + var scopes: List? = null + var token: String? = null + var hashed_token: String? = null + var token_last_eight: String? = null + var note: String? = null + var note_url: String? = null + var created_at: String? = null + var updated_at: String? = null + var fingerprint: String? = null + var app: App? = null + + class App { + var name: String? = null + var url: String? = null + var client_id: String? = null + + override fun toString(): String { + return "app [name=$name, url=$url, client_id=" + client_id + "]" + } + + } + +/* + override fun toString(): String { + return "Token [id=$id, url=$url, scopes=$scopes" + ", token=$token, hashed_token=$hashed_token" + ", token_last_eight=$token_last_eight, note=$note" + ", note_url=$note_url, created_at=$created_at" + ", updated_at=$updated_at, fingerprint=$fingerprint" + ", app=$app]" + } +*/ + + +} diff --git a/code-reader-kt/src/main/java/com/loopeer/codereaderkt/sync/DownloadReceiver.kt b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/sync/DownloadReceiver.kt new file mode 100644 index 0000000..ad037d5 --- /dev/null +++ b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/sync/DownloadReceiver.kt @@ -0,0 +1,25 @@ +package com.loopeer.codereaderkt.sync + +import android.app.DownloadManager +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import com.loopeer.codereaderkt.Navigator + + +class DownloadReceiver : BroadcastReceiver() { + override fun onReceive(p0: Context?, p1: Intent?) { + if (p1!!.hasExtra(DownloadManager.EXTRA_DOWNLOAD_ID)) { + var download: Long = p1.getLongExtra( + DownloadManager.EXTRA_DOWNLOAD_ID, 0 + ) + if (download > 0) { + var i: Intent = Intent(p0, DownloadRepoService::class.java) + i.putExtra(DownloadManager.EXTRA_DOWNLOAD_ID, download) + i.putExtra(Navigator.EXTRA_DOWNLOAD_SERVICE_TYPE, DownloadRepoService.DOWNLOAD_COMPLETE) + p0!!.startService(i) + } + } + } + +} \ No newline at end of file diff --git a/code-reader-kt/src/main/java/com/loopeer/codereaderkt/sync/DownloadRepoService.kt b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/sync/DownloadRepoService.kt new file mode 100644 index 0000000..82e8266 --- /dev/null +++ b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/sync/DownloadRepoService.kt @@ -0,0 +1,283 @@ +package com.loopeer.codereaderkt.sync + +import android.app.DownloadManager +import android.app.Service +import android.content.Context +import android.content.Intent +import android.database.ContentObserver +import android.database.Cursor +import android.net.Uri +import android.os.Build +import android.os.Handler +import android.os.IBinder +import android.util.Log +import com.loopeer.codereaderkt.CodeReaderApplication +import com.loopeer.codereaderkt.Navigator +import com.loopeer.codereaderkt.R +import com.loopeer.codereaderkt.db.CoReaderDbHelper +import com.loopeer.codereaderkt.event.DownloadFailDeleteEvent +import com.loopeer.codereaderkt.event.DownloadProgressEvent +import com.loopeer.codereaderkt.event.DownloadRepoMessageEvent +import com.loopeer.codereaderkt.model.Repo +import com.loopeer.codereaderkt.utils.FileCache +import com.loopeer.codereaderkt.utils.RxBus +import com.loopeer.codereaderkt.utils.Unzip +import rx.Observable +import rx.Subscription +import rx.android.schedulers.AndroidSchedulers +import rx.schedulers.Schedulers +import java.io.File +import java.io.FileInputStream + + +class DownloadRepoService : Service() { + + companion object { + val DOWNLOAD_COMPLETE = 0 + val DOWNLOAD_REPO = 1 + val DOWNLOAD_PROGRESS = 2 + val DOWNLOAD_REMOVE_DOWNLOAD = 3 + } + + + private val TAG = "DownloadRepoService" + + private val DOWNLOAD_CONTENT_URI = Uri.parse("content://downloads/my_downloads") + private val MEDIA_TYPE_ZIP = "application/zip" + private lateinit var mDownloadingRepos: HashMap + private lateinit var mProgressSubscription: Subscription + private lateinit var mDownloadChangeObserver: DownloadChangeObserver + + + override fun onCreate() { + super.onCreate() + mDownloadingRepos = HashMap() + mDownloadChangeObserver = DownloadChangeObserver() + contentResolver.registerContentObserver(DOWNLOAD_CONTENT_URI, true, + mDownloadChangeObserver) + mProgressSubscription = checkDownloadingProgress(this) + } + + override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int { + parseIntent(intent) + return super.onStartCommand(intent, flags, startId) + } + + private fun parseIntent(intent: Intent) { + val inn = intent + val type = inn.getIntExtra(Navigator.EXTRA_DOWNLOAD_SERVICE_TYPE, 0) + val repo = inn.getSerializableExtra(Navigator.EXTRA_REPO) as Repo? + val id: Long = inn.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, 0) + when (type) { + DOWNLOAD_COMPLETE -> { + doRepoDownloadComplete(id, mDownloadingRepos[id]?.absolutePath) + } + DOWNLOAD_REPO -> { + downloadFile(repo!!) + } + DOWNLOAD_PROGRESS -> { + checkDownloadProgress() + } + DOWNLOAD_REMOVE_DOWNLOAD -> { + removeDownloadingRepo(id) + } + } + + } + + private fun doRepoDownloadComplete(id: Long, location: String?) { + CoReaderDbHelper.getInstance(CodeReaderApplication.appContext) + .updateRepoUnzipProgress(id, 1f, true) + RxBus.instance?.send(DownloadProgressEvent(id, true)) + + Observable.create(Observable.OnSubscribe { subscriber -> + var cursor: Cursor? = null + try { + val manager = this@DownloadRepoService.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager + val baseQuery = DownloadManager.Query() + .setFilterById(id) + cursor = manager.query(baseQuery) + val statusColumnId = cursor!!.getColumnIndexOrThrow(DownloadManager.COLUMN_STATUS) + val localFilenameColumnId = cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_LOCAL_FILENAME) + val descName = cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_DESCRIPTION) + val fileUriId = cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_LOCAL_URI) + if (cursor.moveToNext()) { + if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) { + val fileUri = cursor.getString(fileUriId) + val status = cursor.getLong(statusColumnId) + if(status == DownloadManager.STATUS_SUCCESSFUL.toLong()) { + unZipByUri(id, Uri.parse(fileUri), location) + } + } else { + val status = cursor.getLong(statusColumnId) + val path = cursor.getString(localFilenameColumnId) + val name = cursor.getString(descName) + if (status == DownloadManager.STATUS_SUCCESSFUL.toLong()) { + val zipFile = File(path) + val fileCache = FileCache().getInstance() + fileCache.deleteFilesByDirectory(File(fileCache.getCacheDir()!!.getPath() + File.separator + name)) + val decomp = Unzip(FileInputStream(zipFile.path), fileCache.getCacheDir()!!.getPath() + File.separator + name, applicationContext) + decomp.DecompressZip() + if (zipFile.exists()) zipFile.delete() + CoReaderDbHelper.getInstance(CodeReaderApplication.appContext) + .updateRepoUnzipProgress(id, 1f, false) + CoReaderDbHelper.getInstance( + CodeReaderApplication.appContext).resetRepoDownloadId(mDownloadingRepos[id]!!.downloadId) + RxBus.instance?.send(DownloadProgressEvent(id, false)) + RxBus.instance?.send(DownloadRepoMessageEvent( + getString(R.string.repo_download_complete, mDownloadingRepos[id]!!.name))) + } + } + + } + mDownloadingRepos.remove(id) + subscriber.onCompleted() + } catch (e: Exception) { + subscriber.onError(e) + } finally { + cursor!!.close() + } + }) + .onErrorResumeNext(Observable.empty()) + .subscribeOn(Schedulers.io()) + .doOnCompleted { this.checkTaskEmptyToFinish() } + .subscribe() + + + } + + private fun unZipByUri(id: Long, fileUri: Uri?, location: String?) { + val parcelFileDescriptor = contentResolver.openFileDescriptor(fileUri, "r") + val fileDescriptor = parcelFileDescriptor.fileDescriptor + val fileInputStream = FileInputStream(fileDescriptor) + val zipFile = File(fileUri?.path) + val fileCache = FileCache().getInstance() + fileCache.deleteFilesByDirectory(File(location)) + val decomp = Unzip(fileInputStream, location, applicationContext) + decomp.DecompressZip() + if (zipFile.exists()) + zipFile.delete() + CoReaderDbHelper.getInstance(CodeReaderApplication.appContext) + .updateRepoUnzipProgress(id, 1f, false) + CoReaderDbHelper.getInstance( + CodeReaderApplication.appContext).resetRepoDownloadId(mDownloadingRepos[id]!!.downloadId) + RxBus.instance?.send(DownloadProgressEvent(id, false)) + RxBus.instance?.send(DownloadRepoMessageEvent( + getString(R.string.repo_download_complete, mDownloadingRepos[id]!!.name))) + } + + + private fun checkTaskEmptyToFinish() { + if (mDownloadingRepos.isEmpty()) { + stopSelf() + } + } + + private fun downloadFile(repo: Repo) { + val dataFetcher = RemoteRepoFetchers(this, repo.netDownloadUrl, repo.name) + val downloadId: Long = dataFetcher.download() + if (downloadId <= 0) { + CoReaderDbHelper.getInstance(applicationContext).deleteRepo(repo.id!!.toLong()); + return + } + repo.downloadId = downloadId + mDownloadingRepos.put(downloadId, repo) + CoReaderDbHelper.getInstance(applicationContext).updateRepoDownloadId(downloadId, repo.id) + RxBus.instance?.send(DownloadRepoMessageEvent(getString(R.string.repo_download_start, repo.name))) + checkDownloadProgress() + } + + private fun checkDownloadProgress() { + if (mDownloadingRepos.isEmpty()) { + val repos = CoReaderDbHelper.getInstance(this).readRepos() + repos + .filter { it.isDownloading() } + .forEach { mDownloadingRepos.put(it.downloadId, it) } + } + if (mDownloadingRepos.isEmpty()) { + stopSelf() + return + } + if (!mProgressSubscription.isUnsubscribed) { + mProgressSubscription.unsubscribe() + } + mProgressSubscription = checkDownloadingProgress(this) + } + + private fun clearDownloadProgressSubscription() { + if (!mProgressSubscription.isUnsubscribed) { + mProgressSubscription.unsubscribe() + } + } + + override fun onDestroy() { + super.onDestroy() + clearDownloadProgressSubscription() + contentResolver.unregisterContentObserver(mDownloadChangeObserver) + } + + private fun checkDownloadingProgress(context: Context): Subscription { + return Observable.create(Observable.OnSubscribe> { subscriber -> + val downloadManager = context.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager + val repos = ArrayList(mDownloadingRepos.values) + for (repo in repos) { + val q = DownloadManager.Query() + q.setFilterById(repo.downloadId) + + val cursor = downloadManager.query(q) + cursor.moveToFirst() + val bytes_downloaded = cursor.getInt(cursor + .getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR)) + val bytes_total = cursor.getInt( + cursor.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES)) + + val mediaType = cursor.getString(cursor.getColumnIndex(DownloadManager.COLUMN_MEDIA_TYPE)) + val reason = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_REASON)) + val status = cursor.getInt(cursor.getColumnIndex(DownloadManager.COLUMN_STATUS)) + if (status == DownloadManager.STATUS_FAILED + && MEDIA_TYPE_ZIP != mediaType + && reason == DownloadManager.ERROR_UNKNOWN) { + RxBus.instance?.send(DownloadRepoMessageEvent( + getString(R.string.repo_download_fail, repo.name))) + RxBus.instance?.send(DownloadFailDeleteEvent(repo)) + CoReaderDbHelper.getInstance(context).deleteRepo(java.lang.Long.parseLong(repo.id)) + } else if (status != DownloadManager.STATUS_SUCCESSFUL) { + val dl_progress = 1f * bytes_downloaded / bytes_total + repo.factor = dl_progress + } else if (status == DownloadManager.STATUS_SUCCESSFUL) { + repo.factor = 1f + } + if (repo.factor < 0) repo.factor = 0f + if (repo.factor >= 0) { + CoReaderDbHelper.getInstance(CodeReaderApplication.appContext) + .updateRepoDownloadProgress(repo.downloadId, repo.factor) + Log.d("DownloadrepoServiceLog", "" + repo.factor) + RxBus.instance?.send(DownloadProgressEvent(repo.id!!, + repo.downloadId, repo.factor, repo.isUnzip)) + } + cursor.close() + } + subscriber.onCompleted() + }) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe() + } + + private fun removeDownloadingRepo(id: Long) { + val downloadManager = this.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager + val i = downloadManager.remove(id) + if (i > 0) mDownloadingRepos.remove(id) + } + + override fun onBind(p0: Intent?): IBinder? = null + + + internal inner class DownloadChangeObserver : ContentObserver(Handler()) { + + override fun onChange(selfChange: Boolean) { + checkDownloadProgress() + } + + } +} \ No newline at end of file diff --git a/code-reader-kt/src/main/java/com/loopeer/codereaderkt/sync/RemoteRepoFetchers.kt b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/sync/RemoteRepoFetchers.kt new file mode 100644 index 0000000..58c9e4c --- /dev/null +++ b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/sync/RemoteRepoFetchers.kt @@ -0,0 +1,34 @@ +package com.loopeer.codereaderkt.sync + +import android.app.DownloadManager +import android.content.Context +import android.net.Uri +import com.loopeer.codereaderkt.R +import com.loopeer.codereaderkt.event.DownloadRepoMessageEvent +import com.loopeer.codereaderkt.utils.DownloadUrlParser +import com.loopeer.codereaderkt.utils.RxBus + + +class RemoteRepoFetchers(private val mContext: Context, private val mUrl: String?, private val mRepoName: String?) { + private val mDestinationUri: Uri = Uri.fromFile(DownloadUrlParser.getRemoteRepoZipFileName(mRepoName.toString())) + + fun download(): Long { + val manager = mContext.getSystemService(Context.DOWNLOAD_SERVICE) as DownloadManager + val downloadUri = Uri.parse(mUrl) + + var request:DownloadManager.Request ? + try { + request = DownloadManager.Request(downloadUri) + } catch ( e:IllegalArgumentException) { + RxBus.instance?.send(DownloadRepoMessageEvent(mContext.getString(R.string.repo_download_url_parse_error))); + return -1 + } + request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE) + request.setVisibleInDownloadsUi(false) + request.setAllowedNetworkTypes(DownloadManager.Request.NETWORK_MOBILE or DownloadManager.Request.NETWORK_WIFI) + request.setDescription(mRepoName) + request.setDestinationUri(mDestinationUri) + return manager.enqueue(request) + } + +} \ No newline at end of file diff --git a/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/activity/AboutActivity.kt b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/activity/AboutActivity.kt new file mode 100644 index 0000000..a6378f5 --- /dev/null +++ b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/activity/AboutActivity.kt @@ -0,0 +1,72 @@ +package com.loopeer.codereaderkt.ui.activity + +import android.content.Context +import android.content.pm.PackageInfo +import android.content.pm.PackageManager +import android.databinding.DataBindingUtil +import android.os.Bundle +import android.text.SpannableString +import android.text.Spanned +import android.text.method.LinkMovementMethod +import android.view.View +import com.loopeer.codereaderkt.Navigator +import com.loopeer.codereaderkt.R +import com.loopeer.codereaderkt.databinding.ActivityAboutBinding +import com.loopeer.codereaderkt.utils.CustomTextUtils +import com.loopeer.directorychooser.ColorClickableSpan + + +class AboutActivity: BaseActivity() { + + lateinit var binding : ActivityAboutBinding + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = DataBindingUtil.setContentView(this, R.layout.activity_about) + + setUpVersion(this) + setUpTextSpan() + } + + private fun setUpTextSpan() { + val aboutContent = resources.getString(R.string.about_content) + val indexSource = CustomTextUtils.calculateTextStartEnd(aboutContent, resources.getString(R.string.about_coreader)) + val signPolicyTipSpan = SpannableString(aboutContent) + signPolicyTipSpan.setSpan(object : ColorClickableSpan(this, R.color.colorPrimary) { + override fun onClick(widget: View) { + Navigator().startWebActivity(this@AboutActivity, getString(R.string.about_coreader_github_url)) + } + }, indexSource[0], indexSource[1], Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) + + val indexEmail = CustomTextUtils.calculateTextStartEnd(aboutContent, + resources.getString(R.string.about_email)) + signPolicyTipSpan.setSpan(object : ColorClickableSpan(this, R.color.colorPrimary) { + override fun onClick(widget: View) { + Navigator().startComposeEmail(this@AboutActivity, + arrayOf(getString(R.string.about_email)), + getString(R.string.app_name), + getString(R.string.about_email_content_tip)) + } + }, indexEmail[0], indexEmail[1], Spanned.SPAN_EXCLUSIVE_EXCLUSIVE) + binding.textAboutContent.text = signPolicyTipSpan + binding.textAboutContent.movementMethod = LinkMovementMethod.getInstance() + } + + private fun setUpVersion(context: Context) { + val packageManager = context.packageManager + var packInfo: PackageInfo? = null + try { + packInfo = packageManager.getPackageInfo(context.packageName, 0) + } catch (e: PackageManager.NameNotFoundException) { + e.printStackTrace() + } + + var version = "" + var code = "" + if (packInfo != null) { + version = packInfo.versionName + code = Integer.valueOf(packInfo.versionCode)!!.toString() + } + + binding.textAboutVersion.text = "V$version-$code" + } +} \ No newline at end of file diff --git a/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/activity/AddRepoActivity.kt b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/activity/AddRepoActivity.kt new file mode 100644 index 0000000..55c35af --- /dev/null +++ b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/activity/AddRepoActivity.kt @@ -0,0 +1,62 @@ +package com.loopeer.codereaderkt.ui.activity + +import android.databinding.DataBindingUtil +import android.os.Bundle +import android.text.Editable +import android.text.TextUtils +import android.view.View +import com.loopeer.codereaderkt.Navigator +import com.loopeer.codereaderkt.R +import com.loopeer.codereaderkt.databinding.ActivityAddRepoBinding +import com.loopeer.codereaderkt.model.Repo +import com.loopeer.codereaderkt.ui.view.AddRepoChecker +import com.loopeer.codereaderkt.ui.view.Checker +import com.loopeer.codereaderkt.ui.view.TextWatcherImpl +import com.loopeer.codereaderkt.utils.DownloadUrlParser +import com.loopeer.codereaderkt.utils.FileCache + + +class AddRepoActivity : BaseActivity(), Checker.CheckObserver { + + lateinit var mAddRepoChecker: AddRepoChecker + private lateinit var binding: ActivityAddRepoBinding + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + binding = DataBindingUtil.setContentView(this, R.layout.activity_add_repo) + mAddRepoChecker = AddRepoChecker(this) + + + binding.editAddRepoName.addTextChangedListener(object : TextWatcherImpl() { + override fun afterTextChanged(editable: Editable) { + super.afterTextChanged(editable) + mAddRepoChecker.repoName = editable.toString() + } + }) + binding.editAddRepoUrl.addTextChangedListener(object : TextWatcherImpl() { + override fun afterTextChanged(editable: Editable) { + super.afterTextChanged(editable) + mAddRepoChecker.repoDownloadUrl = editable.toString() + } + }) + } + + fun onDownClick(view: View) { + hideSoftInputMethod() + if (TextUtils.isEmpty(mAddRepoChecker.repoName)&&TextUtils.isEmpty(mAddRepoChecker.repoDownloadUrl)) run { + //未填写文件名则默认为项目原名 + if (!TextUtils.isEmpty(mAddRepoChecker.repoDownloadUrl?.trim()) && !DownloadUrlParser.parseGithubUrlAndDownload(this@AddRepoActivity, mAddRepoChecker.repoDownloadUrl?.trim()!!)) { + showMessage(getString(R.string.repo_download_url_parse_error)) + } + } else { + val repo = Repo( + mAddRepoChecker.repoName?.trim(), FileCache().getInstance().getRepoAbsolutePath(mAddRepoChecker.repoName!!), DownloadUrlParser.parseGithubDownloadUrl(mAddRepoChecker.repoDownloadUrl?.trim()!!), true, 0) + Navigator().startDownloadNewRepoService(this, repo) + } + } + override fun check(b: Boolean) { + binding.btnAddRepo.isEnabled = b + } + +} \ No newline at end of file diff --git a/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/activity/BaseActivity.kt b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/activity/BaseActivity.kt new file mode 100644 index 0000000..c6f8d48 --- /dev/null +++ b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/activity/BaseActivity.kt @@ -0,0 +1,175 @@ +package com.loopeer.codereaderkt.ui.activity + +import android.content.Context +import android.content.Intent +import android.os.Bundle +import android.support.design.widget.CoordinatorLayout +import android.support.design.widget.Snackbar +import android.support.v7.app.ActionBar +import android.support.v7.app.AppCompatActivity +import android.support.v7.widget.Toolbar +import android.text.TextUtils +import android.view.MenuItem +import android.view.inputmethod.InputMethodManager +import com.loopeer.codereaderkt.R +import com.loopeer.codereaderkt.event.DownloadRepoMessageEvent +import com.loopeer.codereaderkt.event.ThemeRecreateEvent +import com.loopeer.codereaderkt.ui.view.ProgressLoading +import com.loopeer.codereaderkt.utils.RxBus +import rx.Subscription +import rx.android.schedulers.AndroidSchedulers +import rx.subscriptions.CompositeSubscription + + +open class BaseActivity : AppCompatActivity() { + + internal var mToolbar: Toolbar? = null + private var mCoordinatorContainer: CoordinatorLayout? = null + + + private val mAllSubscription = CompositeSubscription() + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + registerSubscription( + RxBus.instance + ?.toObservable() + ?.filter({ o -> o is DownloadRepoMessageEvent }) + ?.map({ o -> o as DownloadRepoMessageEvent }) + ?.observeOn(AndroidSchedulers.mainThread()) + ?.doOnNext({ o -> showMessage(o.message) }) + ?.subscribe()!!) + + registerSubscription( + RxBus.instance + ?.toObservable() + ?.filter({ o -> o is ThemeRecreateEvent }) + ?.observeOn(AndroidSchedulers.mainThread()) + ?.doOnNext({ o -> recreate() }) + ?.subscribe()!!) + //打开app恢复下载 + } + + override fun onContentChanged() { + super.onContentChanged() + mToolbar = findViewById(R.id.toolbar) as Toolbar? + mCoordinatorContainer = findViewById(R.id.view_coordinator_container) as CoordinatorLayout? + if (mToolbar != null) { + setSupportActionBar(mToolbar) + onSetupActionBar(this.supportActionBar!!) + } + + val title = intent.getStringExtra(Intent.EXTRA_TITLE) + if (!TextUtils.isEmpty(title)) { + setTitle(title) + } + + } + + private fun onSetupActionBar(actionBar: ActionBar) { + actionBar.setDisplayHomeAsUpEnabled(true) + } + + protected fun registerSubscription(subscription: Subscription) { + mAllSubscription.add(subscription) + + } + + protected fun unregisterSubscription(subscription: Subscription) { + mAllSubscription.remove(subscription) + } + + private fun clearSubscription() { + mAllSubscription.clear() + } + + protected open fun reCreateRefresh() { + + } + + override fun onDestroy() { + super.onDestroy() + clearSubscription() + if (isProgressShow() && mProgressLoading != null) { + dismissProgressLoading() + mProgressLoading = null + } + } + + private var mProgressLoading: ProgressLoading? = null + private lateinit var mUnBackProgressLoading: ProgressLoading + private var progressShow: Boolean = false + + fun showProgressLoading(resId: Int) { + showProgressLoading(getString(resId)) + } + + fun showProgressLoading(message: String) { + if (mProgressLoading == null) { + mProgressLoading = ProgressLoading(this, R.style.ProgressLoadingTheme) + mProgressLoading!!.setCanceledOnTouchOutside(true) + mProgressLoading!!.setOnCancelListener({ progressShow = false }) + } + if (!TextUtils.isEmpty(message)) { + mProgressLoading!!.setMessage(message) + } else { + mProgressLoading!!.setMessage("") + } + progressShow = true + mProgressLoading!!.show() + } + + private fun isProgressShow(): Boolean = progressShow + + fun dismissProgressLoading() { + if (mProgressLoading != null && !isFinishing) { + progressShow = false + mProgressLoading!!.dismiss() + } + } + + fun showUnBackProgressLoading(resId: Int) { + showUnBackProgressLoading(getString(resId)) + } + + // 按返回键不可撤销的 + private fun showUnBackProgressLoading(message: String) { + if (!TextUtils.isEmpty(message)) { + mUnBackProgressLoading.setMessage(message) + } else { + mUnBackProgressLoading.setMessage("") + } + mUnBackProgressLoading.show() + } + + fun dismissUnBackProgressLoading() { + if (!isFinishing) { + mUnBackProgressLoading.dismiss() + } + } + + fun hideSoftInputMethod() { + val imm = getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager + if (imm.isActive) { + imm.hideSoftInputFromWindow(currentFocus!!.windowToken, 0) + } + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + if (item.itemId == android.R.id.home) { + finish() + return true + } + return super.onOptionsItemSelected(item) + } + + protected fun showMessage(message: String) { + if (mCoordinatorContainer != null) + Snackbar.make(mCoordinatorContainer!!, message, Snackbar.LENGTH_SHORT) + .setAction(R.string.snackbar_action, { }) + .show() + } + + +} \ No newline at end of file diff --git a/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/activity/CodeReadActivity.kt b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/activity/CodeReadActivity.kt new file mode 100644 index 0000000..cbf38bf --- /dev/null +++ b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/activity/CodeReadActivity.kt @@ -0,0 +1,147 @@ +package com.loopeer.codereaderkt.ui.activity + +import android.databinding.DataBindingUtil +import android.os.Build +import android.os.Bundle +import android.support.v4.view.GravityCompat +import android.view.Menu +import android.view.MenuItem +import android.view.View +import android.view.WindowManager +import android.widget.FrameLayout +import android.widget.Toast +import com.loopeer.codereaderkt.Navigator +import com.loopeer.codereaderkt.R +import com.loopeer.codereaderkt.databinding.ActivityCodeReadBinding +import com.loopeer.codereaderkt.db.CoReaderDbHelper +import com.loopeer.codereaderkt.model.DirectoryNode +import com.loopeer.codereaderkt.model.Repo +import com.loopeer.codereaderkt.ui.fragment.CodeReadFragment +import com.loopeer.codereaderkt.ui.view.DirectoryNavDelegate +import com.loopeer.codereaderkt.utils.DeviceUtils + + +class CodeReadActivity : BaseActivity(), DirectoryNavDelegate.FileClickListener, DirectoryNavDelegate.LoadFileCallback { + + internal var mLeftSheet: View? = null + internal var mContainer: FrameLayout? = null + + private lateinit var mFragment: CodeReadFragment + private var mDirectoryNode: DirectoryNode?=null + private lateinit var mSelectedNode: DirectoryNode + + private lateinit var mDirectoryNavDelegate: DirectoryNavDelegate + + lateinit var binding: ActivityCodeReadBinding + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = DataBindingUtil.setContentView(this, R.layout.activity_code_read) + setupStatusBar() + + mDirectoryNavDelegate = DirectoryNavDelegate(binding.directoryView, this) + mDirectoryNavDelegate.setLoadFileCallback(this) + createFragment(DirectoryNode()) + parseIntent(savedInstanceState) + } + + private fun setupStatusBar() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) + return + + window.addFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS) + binding.directoryView.setPadding(0, DeviceUtils.statusBarHeight, 0, 0) + binding.directoryView.clipToPadding = true + } + + private fun parseIntent(savedInstanceState: Bundle?) { + if (savedInstanceState != null) { + mDirectoryNode = savedInstanceState.getSerializable(Navigator.EXTRA_DIRETORY_ROOT) as DirectoryNode + mSelectedNode = savedInstanceState.getSerializable(Navigator.EXTRA_DIRETORY_SELECTING) as DirectoryNode + val rootNodeInstance = savedInstanceState.getSerializable(Navigator.EXTRA_DIRETORY_ROOT_NODE_INSTANCE) as DirectoryNode + mFragment.updateRootNode(mDirectoryNode!!) + mDirectoryNavDelegate.resumeDirectoryState(rootNodeInstance) + doOpenFile(mSelectedNode) + return + } + val intent = intent + val repo = intent.getSerializableExtra(Navigator.EXTRA_REPO) as Repo + CoReaderDbHelper.getInstance(this).updateRepoLastModify(java.lang.Long.valueOf(repo.id), + System.currentTimeMillis()) + mDirectoryNode = repo.toDirectoryNode() + mFragment.updateRootNode(mDirectoryNode!!) + mDirectoryNavDelegate.updateData(mDirectoryNode!!) + } + + override fun onSaveInstanceState(outState: Bundle) { + super.onSaveInstanceState(outState) + outState.putSerializable(Navigator.EXTRA_DIRETORY_ROOT, mDirectoryNode) + outState.putSerializable(Navigator.EXTRA_DIRETORY_SELECTING, mSelectedNode) + outState.putSerializable(Navigator.EXTRA_DIRETORY_ROOT_NODE_INSTANCE, mDirectoryNavDelegate.directoryNodeInstance) + } + + override fun onBackPressed() { + if (binding.drawerLayout.isDrawerOpen(GravityCompat.START)) { + binding.drawerLayout.closeDrawer(GravityCompat.START) + } else { + super.onBackPressed() + } + } + + override fun onCreateOptionsMenu(menu: Menu): Boolean { + menuInflater.inflate(R.menu.menu_code_read_go_out, menu) + return true + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + val id = item.itemId + if (id == R.id.action_go_out) { + finish() + return true + } + if (id == android.R.id.home) { + if (!binding.drawerLayout.isDrawerOpen(GravityCompat.START)) + binding.drawerLayout.openDrawer(GravityCompat.START) + return true + } + return super.onOptionsItemSelected(item) + } + + override fun onDestroy() { + super.onDestroy() + mDirectoryNavDelegate.clearSubscription() + //mDirectoryNavDelegate = null + } + + override fun doOpenFile(node: DirectoryNode?) { + title = if (node == null) mDirectoryNode?.name else node.name + mSelectedNode = node!! + loadCodeData(node) + } + + private fun loadCodeData(node: DirectoryNode?) { + binding.drawerLayout!!.closeDrawer(GravityCompat.START) + if (node != null) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + mFragment.openFile(node) + } + } + } + + private fun createFragment(node: DirectoryNode) { + mFragment = CodeReadFragment.newInstance(node, mDirectoryNode) + mFragment.arguments = intent.extras + supportFragmentManager.beginTransaction() + .add(R.id.container_code_read, mFragment).commit() + } + + override fun onFileOpenStart() { + if (mFragment.isVisible) + mFragment.getCodeContentLoader()!!.showProgress() + } + + override fun onFileOpenEnd() { + + } + +} \ No newline at end of file diff --git a/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/activity/LoginActivity.kt b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/activity/LoginActivity.kt new file mode 100644 index 0000000..9183f48 --- /dev/null +++ b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/activity/LoginActivity.kt @@ -0,0 +1,126 @@ +package com.loopeer.codereaderkt.ui.activity + +import android.databinding.DataBindingUtil +import android.os.Bundle +import android.text.Editable +import android.util.Log +import android.view.View +import com.loopeer.codereaderkt.R +import com.loopeer.codereaderkt.api.ServiceFactory +import com.loopeer.codereaderkt.api.service.GithubService +import com.loopeer.codereaderkt.databinding.ActivityLoginBinding +import com.loopeer.codereaderkt.model.Empty +import com.loopeer.codereaderkt.model.Token +import com.loopeer.codereaderkt.ui.view.Checker +import com.loopeer.codereaderkt.ui.view.LoginChecker +import com.loopeer.codereaderkt.ui.view.TextWatcherImpl +import com.loopeer.codereaderkt.utils.Base64 +import com.loopeer.codereaderkt.utils.SnackbarUtils +import retrofit2.Response +import rx.Observable +import rx.android.schedulers.AndroidSchedulers +import rx.schedulers.Schedulers +import java.util.* + + +class LoginActivity : BaseActivity(), Checker.CheckObserver { + + + //登陆本身就有问题 + private val TAG = "LoginActivity" + + lateinit var binding: ActivityLoginBinding + + private lateinit var mGithubService: GithubService + + private val TOKEN_NOTE = "CodeReader APP Token" + private val SCOPES = arrayOf("public_repo", "repo", "user", "gist") + + private var mLoginChecker: LoginChecker? = null + private var mBase64Str: String? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + binding = DataBindingUtil.setContentView(this, R.layout.activity_login) + + mGithubService = ServiceFactory().getGithubService() + mLoginChecker = LoginChecker(this) + + binding.editLoginAccount.addTextChangedListener(object : TextWatcherImpl() { + override fun afterTextChanged(editable: Editable) { + super.afterTextChanged(editable) + mLoginChecker!!.username = editable.toString() + } + }) + binding.editLoginPassword.addTextChangedListener(object : TextWatcherImpl() { + override fun afterTextChanged(editable: Editable) { + super.afterTextChanged(editable) + mLoginChecker!!.password = editable.toString() + } + }) + + } + + private fun createToken(base64: String) { + + val token = Token() + token.note = (TOKEN_NOTE) + token.scopes = (Arrays.asList(*SCOPES)) + + registerSubscription( + mGithubService.createToken(token, base64) + .subscribeOn(Schedulers.io()) + .doOnSubscribe { showProgressLoading("") } + .observeOn(AndroidSchedulers.mainThread()) + .doAfterTerminate({ this.dismissProgressLoading() }) + .doOnNext { tokenResponse -> + when { + tokenResponse.isSuccessful -> { + Log.d(TAG, tokenResponse.body().toString()) + val t = tokenResponse.body().token + SnackbarUtils.show(binding.layoutContainer, t.toString()) + } + tokenResponse.code() == 401 -> SnackbarUtils.show(binding.layoutContainer, R.string.login_auth_error) + tokenResponse.code() == 403 -> SnackbarUtils.show(binding.layoutContainer, R.string.login_over_auth_error) + tokenResponse.code() == 422 -> findCertainTokenID(base64) + } + } + .subscribe() + ) + } + + private fun findCertainTokenID(base64: String) { + registerSubscription( + mGithubService.listToken(base64) + .flatMap> { listResponse -> + listResponse.body() + .filter { TOKEN_NOTE == it.note } + .forEach { return@flatMap mGithubService.removeToken(base64, it.id.toString()) } + + Observable.empty>() + } + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .doOnNext { emptyResponse -> + if (emptyResponse.code() == 204) { + createToken(base64) + } + } + .subscribe() + ) + } + + fun onSignInClick(view: View) { + val username = binding.editLoginAccount.text.toString() + val password = binding.editLoginPassword.text.toString() + mBase64Str = "Basic " + Base64.encode(username + ':' + password) + createToken(mBase64Str!!) + } + + override fun check(b: Boolean) { + //登陆按钮是否可点击 + binding.btnSignIn.isEnabled = b + } + +} \ No newline at end of file diff --git a/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/activity/MainActivity.kt b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/activity/MainActivity.kt new file mode 100644 index 0000000..c671152 --- /dev/null +++ b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/activity/MainActivity.kt @@ -0,0 +1,179 @@ +package com.loopeer.codereaderkt.ui.activity + +import android.Manifest +import android.app.Activity +import android.content.Intent +import android.content.pm.PackageManager +import android.databinding.DataBindingUtil +import android.os.Bundle +import android.support.v4.app.ActivityCompat +import android.support.v4.content.ContextCompat +import android.support.v7.widget.LinearLayoutManager +import android.support.v7.widget.RecyclerView +import android.view.Menu +import android.view.MenuItem +import android.view.View +import android.widget.ViewAnimator +import com.loopeer.codereaderkt.CodeReaderApplication +import com.loopeer.codereaderkt.Navigator +import com.loopeer.codereaderkt.R +import com.loopeer.codereaderkt.databinding.ActivityMainBinding +import com.loopeer.codereaderkt.db.CoReaderDbHelper +import com.loopeer.codereaderkt.event.DownloadFailDeleteEvent +import com.loopeer.codereaderkt.model.Repo +import com.loopeer.codereaderkt.sync.DownloadRepoService +import com.loopeer.codereaderkt.ui.adapter.ItemTouchHelperCallback +import com.loopeer.codereaderkt.ui.adapter.MainLatestAdapter +import com.loopeer.codereaderkt.ui.decoration.DividerItemDecoration +import com.loopeer.codereaderkt.ui.decoration.DividerItemDecorationMainList +import com.loopeer.codereaderkt.ui.loader.ILoadHelper +import com.loopeer.codereaderkt.ui.loader.RecyclerLoader +import com.loopeer.codereaderkt.utils.RxBus +import com.loopeer.directorychooser.FileNod +import com.loopeer.directorychooser.NavigatorChooser +import com.loopeer.itemtouchhelperextension.ItemTouchHelperExtension +import rx.android.schedulers.AndroidSchedulers + + +class MainActivity : BaseActivity() { + + private val TAG = "MainActivity" + private val MY_PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE = 1000 + private lateinit var binding: ActivityMainBinding + + private lateinit var mRecyclerLoader: ILoadHelper + private lateinit var mMainLatestAdapter: MainLatestAdapter + + private lateinit var mItemTouchHelper: ItemTouchHelperExtension + private lateinit var mCallback: ItemTouchHelperExtension.Callback + + private lateinit var mRecyclerView: RecyclerView + private lateinit var mAnimatorRecyclerContent: ViewAnimator + @SuppressWarnings("unused") + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = DataBindingUtil.setContentView(this, R.layout.activity_main) + + Navigator().startDownloadRepoService(this, DownloadRepoService.DOWNLOAD_PROGRESS) + + mRecyclerView = findViewById(R.id.view_recycler) as RecyclerView + mAnimatorRecyclerContent = findViewById(R.id.animator_recycler_content) as ViewAnimator + supportActionBar?.setDisplayHomeAsUpEnabled(false) + if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { + if (ActivityCompat.shouldShowRequestPermissionRationale(this, + Manifest.permission.WRITE_EXTERNAL_STORAGE)) { + } else { + ActivityCompat.requestPermissions(this, + arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), + MY_PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE) + } + } + } + + override fun onCreateOptionsMenu(menu: Menu): Boolean { + menuInflater.inflate(R.menu.menu_repo_add, menu) + menuInflater.inflate(R.menu.menu_settings, menu) + menuInflater.inflate(R.menu.menu_github, menu) + return super.onCreateOptionsMenu(menu) + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + when (item.itemId) { + R.id.action_settings -> Navigator().startSettingActivity(this) + R.id.action_repo_add -> Navigator().startAddRepoActivity(this) + R.id.action_github -> Navigator().startLoginActivity(this) + } + return super.onOptionsItemSelected(item) + } + + override fun onPostCreate(savedInstanceState: Bundle?) { + super.onPostCreate(savedInstanceState) + setUpView() + registerSubscription( + RxBus.instance?.toObservable() + ?.filter({ o -> o is DownloadFailDeleteEvent }) + ?.map({ o -> (o as DownloadFailDeleteEvent).deleteRepo }) + ?.observeOn(AndroidSchedulers.mainThread()) + ?.doOnNext({ mMainLatestAdapter.deleteRepo(it) }) + ?.subscribe()!! + ) + } + + override fun onResume() { + super.onResume() + mRecyclerLoader.showProgress() + loadLocalData() + } + + private fun setUpView() { + mRecyclerLoader = RecyclerLoader(mAnimatorRecyclerContent) + mRecyclerView.layoutManager = LinearLayoutManager(this) + mMainLatestAdapter = MainLatestAdapter(this) + mRecyclerView.adapter = mMainLatestAdapter + mRecyclerView.addItemDecoration(DividerItemDecorationMainList(this, + DividerItemDecoration.VERTICAL_LIST, resources.getDimensionPixelSize(R.dimen.repo_list_divider_start), -1, -1)) + mItemTouchHelper = createItemTouchHelper() + mItemTouchHelper.attachToRecyclerView(mRecyclerView) + } + + private fun createItemTouchHelper(): ItemTouchHelperExtension { + mCallback = createCallback() + return ItemTouchHelperExtension(mCallback) + } + + private fun createCallback(): ItemTouchHelperExtension.Callback = ItemTouchHelperCallback() + + private fun loadLocalData() { + val repos = CoReaderDbHelper.getInstance(CodeReaderApplication.appContext).readRepos() + setUpContent(repos) + } + + override fun reCreateRefresh() { + super.reCreateRefresh() + mRecyclerView.recycledViewPool.clear() + mMainLatestAdapter.notifyDataSetChanged() + } + + private fun setUpContent(repos: List) { + mRecyclerLoader.showContent() + mMainLatestAdapter.updateData(repos) + } + + fun onFabClick(view: View) { + doSelectFile() + } + + private fun doSelectFile() { + NavigatorChooser.startDirectoryFileChooserActivity(this) + } + + public override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent) { + super.onActivityResult(requestCode, resultCode, data) + when (requestCode) { + NavigatorChooser.DIRECTORY_FILE_SELECT_CODE -> if (resultCode == Activity.RESULT_OK) { + val node = data.getSerializableExtra(NavigatorChooser.EXTRA_FILE_NODE) as FileNod + val repo = Repo().parse(node) + repo.id = CoReaderDbHelper.getInstance(this).insertRepo(repo).toString() + Navigator().startCodeReadActivity(this@MainActivity, repo) + } + } + } + + override fun onPause() { + super.onPause() + mMainLatestAdapter.clearSubscription() + } + + override fun onRequestPermissionsResult(requestCode: Int, + permissions: Array, grantResults: IntArray) { + when (requestCode) { + MY_PERMISSIONS_REQUEST_WRITE_EXTERNAL_STORAGE -> { + if (grantResults.isNotEmpty() && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + } else { + } + return + } + } + } + +} \ No newline at end of file diff --git a/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/activity/RepositoryFragment.kt b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/activity/RepositoryFragment.kt new file mode 100644 index 0000000..3156aff --- /dev/null +++ b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/activity/RepositoryFragment.kt @@ -0,0 +1,125 @@ +package com.loopeer.codereaderkt.ui.activity + +import android.os.Build +import android.os.Bundle +import android.support.annotation.Nullable +import android.support.v7.widget.LinearLayoutManager +import android.support.v7.widget.RecyclerView +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import com.loopeer.codereaderkt.R +import com.loopeer.codereaderkt.api.ServiceFactory +import com.loopeer.codereaderkt.api.service.GithubService +import com.loopeer.codereaderkt.model.Repository +import com.loopeer.codereaderkt.ui.adapter.RepositoryAdapter +import com.loopeer.codereaderkt.ui.decoration.DividerItemDecoration +import com.loopeer.codereaderkt.ui.fragment.BaseFragment +import com.loopeer.codereaderkt.utils.PageLinkParser +import rx.android.schedulers.AndroidSchedulers +import rx.schedulers.Schedulers + + +class RepositoryFragment : BaseFragment() { + //TODO打开时会崩溃,未找到原因; 原因是BaseFragment继承的Fragment应该引入v4.Fragment而不是app.Fragment + + private val PAGE_SIZE = 10 + private lateinit var mViewRecycler: RecyclerView + + private lateinit var mRepositoryAdapter: RepositoryAdapter + private lateinit var mGithubService: GithubService + + private lateinit var mSearchText: String + + private lateinit var mRepositories: ArrayList + + private lateinit var mPageLinkParser: PageLinkParser + + private var mIsLoading: Boolean = false + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + mRepositories = ArrayList() + mRepositoryAdapter = RepositoryAdapter(activity) + mGithubService = ServiceFactory().getGithubService() + } + + override fun onCreateView(inflater: LayoutInflater?, @Nullable container: ViewGroup?, @Nullable savedInstanceState: Bundle?): View? = + inflater?.inflate(R.layout.activity_search_result, container, false) + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + mViewRecycler = view.findViewById(R.id.view_recycler) + setupRecyclerView() + } + + private fun setupRecyclerView() { + mViewRecycler.layoutManager = LinearLayoutManager(activity) + mViewRecycler.adapter = mRepositoryAdapter + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + mViewRecycler.addItemDecoration(DividerItemDecoration(context)) + } + + mViewRecycler.addOnScrollListener(object : RecyclerView.OnScrollListener() { + override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) { + super.onScrollStateChanged(recyclerView, newState) + val layoutManager = recyclerView.layoutManager as LinearLayoutManager + if (layoutManager.findLastVisibleItemPosition() == mRepositoryAdapter.itemCount - 1 && isHasMore()) { + if (!mIsLoading) + requestData(mPageLinkParser.next) + } + } + }) + } + + fun setSearchText(searchText: String) { + mSearchText = searchText + requestData(1) + } + + fun isHasMore(): Boolean = mPageLinkParser.next != 0 && mPageLinkParser.remain != 0 + + private fun requestData(page: Int) { + mIsLoading = true + + if (page == 1) { + mRepositories.clear() + showProgressLoading("") + } + + registerSubscription(mGithubService.repositories(mSearchText, null.toString(), null.toString(), page, PAGE_SIZE) + .filter { baseListResponseResponse -> baseListResponseResponse.isSuccessful } + .map> { baseListResponseResponse -> + mPageLinkParser = PageLinkParser(baseListResponseResponse) + baseListResponseResponse.body().items + } + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe({ repositories -> + mRepositories.addAll(repositories) + mRepositoryAdapter.setHasMore(isHasMore()) + mRepositoryAdapter.updateData(mRepositories) + dismissProgressLoading() + + mIsLoading = false + }) { throwable -> + throwable.printStackTrace() + dismissProgressLoading() + + mIsLoading = false + }) + } + + override fun showProgressLoading(message: String) { + mViewRecycler.visibility = View.INVISIBLE + super.showProgressLoading(message) + getProgressLoaing()?.setCanceledOnTouchOutside(false) + } + + override fun dismissProgressLoading() { + mViewRecycler.visibility = View.VISIBLE + super.dismissProgressLoading() + } + + +} \ No newline at end of file diff --git a/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/activity/SearchActivity.kt b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/activity/SearchActivity.kt new file mode 100644 index 0000000..d12e489 --- /dev/null +++ b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/activity/SearchActivity.kt @@ -0,0 +1,54 @@ +package com.loopeer.codereaderkt.ui.activity + +import android.app.SearchManager +import android.content.Context +import android.os.Bundle +import android.support.v7.widget.SearchView +import android.text.TextUtils +import android.view.Menu +import android.view.MenuItem +import com.loopeer.codereaderkt.R + + +class SearchActivity : BaseActivity(), SearchView.OnQueryTextListener { + + private lateinit var mRepositoryFragment: RepositoryFragment + private lateinit var mSearchView: SearchView + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_search) + mRepositoryFragment = supportFragmentManager + .findFragmentById(R.id.fragment_repository) as RepositoryFragment + } + + override fun onCreateOptionsMenu(menu: Menu): Boolean { + menuInflater.inflate(R.menu.menu_file_search, menu) + val searchManager = getSystemService(Context.SEARCH_SERVICE) as SearchManager + mSearchView = menu.findItem(R.id.action_search).actionView as SearchView + mSearchView.setSearchableInfo(searchManager.getSearchableInfo(componentName)) + mSearchView.onActionViewExpanded() + mSearchView.maxWidth = Integer.MAX_VALUE + mSearchView.setOnQueryTextListener(this) + return true + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + if (item.itemId == android.R.id.home) { + this.finish() + return true + } + return super.onOptionsItemSelected(item) + } + + + override fun onQueryTextSubmit(query: String): Boolean { + if (!TextUtils.isEmpty(query)) { + mRepositoryFragment.setSearchText(query) + mSearchView.clearFocus() + } + return true + } + + override fun onQueryTextChange(newText: String): Boolean = false +} \ No newline at end of file diff --git a/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/activity/SettingActivity.kt b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/activity/SettingActivity.kt new file mode 100644 index 0000000..d00ab64 --- /dev/null +++ b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/activity/SettingActivity.kt @@ -0,0 +1,97 @@ +package com.loopeer.codereaderkt.ui.activity + +import android.databinding.DataBindingUtil +import android.os.Bundle +import android.support.v7.app.AppCompatDelegate +import android.view.View +import android.widget.SeekBar +import com.loopeer.codereaderkt.Navigator +import com.loopeer.codereaderkt.R +import com.loopeer.codereaderkt.databinding.ActivitySettingBinding +import com.loopeer.codereaderkt.event.ThemeRecreateEvent +import com.loopeer.codereaderkt.ui.view.ThemeChooser +import com.loopeer.codereaderkt.utils.PrefUtils +import com.loopeer.codereaderkt.utils.RxBus +import com.loopeer.codereaderkt.utils.ThemeUtils + + +class SettingActivity : BaseActivity(), SeekBar.OnSeekBarChangeListener, ThemeChooser.OnItemSelectListener { + + + lateinit var binding: ActivitySettingBinding + private var mThemeChooser: ThemeChooser? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + binding = DataBindingUtil.setContentView(this, R.layout.activity_setting) + + mThemeChooser = ThemeChooser(this, this) + mThemeChooser!!.addItem(binding.viewSettingThemeDay.id, ThemeUtils.THEME_DAY) + mThemeChooser!!.addItem(binding.viewSettingThemeNight.id, ThemeUtils.THEME_NIGHT) + initViewData() + setUpView() + } + + private fun initViewData() { + binding.checkboxShowLineNumber.isChecked = PrefUtils.getPrefDisplayLineNumber(this) + binding.checkboxMenloFont.isChecked = PrefUtils.getPrefMenlofont(this) + val fontSize = PrefUtils.getPrefFontSize(this).toInt() + binding.seekbarSettingFontSize.progress = fontSize + binding.textSettingFontCurrent.text = fontSize.toString() + binding.textSettingFontSizeTemp.textSize = fontSize.toFloat() + mThemeChooser!!.onItemSelectByTag(PrefUtils.getPrefTheme(this)) + } + + private fun setUpView() { + binding.checkboxShowLineNumber.setOnCheckedChangeListener({ _, b -> PrefUtils.setPrefDisplayLineNumber(this@SettingActivity, b) }) + binding.checkboxMenloFont.setOnCheckedChangeListener({ _, b -> PrefUtils.setPrefMenlofont(this@SettingActivity, b) }) + binding.seekbarSettingFontSize.setOnSeekBarChangeListener(this) + } + + override fun onProgressChanged(seekBar: SeekBar, i: Int, b: Boolean) { + binding.textSettingFontSizeTemp.textSize = i.toFloat() + PrefUtils.setPrefFontSize(this, i.toFloat()) + binding.textSettingFontCurrent.text = i.toString() + } + + + override fun onStartTrackingTouch(p0: SeekBar?) { + + } + + override fun onStopTrackingTouch(p0: SeekBar?) { + + } + + fun onSettingLineNumberClick(view: View) { + binding.checkboxShowLineNumber.isChecked = !binding.checkboxShowLineNumber.isChecked + } + + fun onSettingUseMenloClick(view: View) { + binding.checkboxMenloFont.isChecked = !binding.checkboxMenloFont.isChecked + } + + fun onThemeSelectClick(view: View) { + mThemeChooser!!.onItemSelect(view) + } + + fun onAboutClick(view: View) { + Navigator().startAboutActivity(this)//不能写成Navigator.startAboutActivity(~) 要加上括号 +// Toast.makeText(this, "onAboutClick", Toast.LENGTH_SHORT).show() + } + + override fun onItemSelect(id: Int, tag: String) { + if (PrefUtils.getPrefTheme(this) == tag) { + return + } + AppCompatDelegate.setDefaultNightMode(if (tag == ThemeUtils.THEME_DAY) + AppCompatDelegate.MODE_NIGHT_NO + else + AppCompatDelegate.MODE_NIGHT_YES) + + RxBus.instance?.send(ThemeRecreateEvent()) + PrefUtils.setPrefTheme(this, tag) + //怎么能立即刷新主题? + } +} \ No newline at end of file diff --git a/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/activity/SimpleWebActivity.kt b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/activity/SimpleWebActivity.kt new file mode 100644 index 0000000..0accfec --- /dev/null +++ b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/activity/SimpleWebActivity.kt @@ -0,0 +1,173 @@ +package com.loopeer.codereaderkt.ui.activity + +import android.annotation.TargetApi +import android.app.SearchManager +import android.content.Context +import android.content.Intent +import android.databinding.DataBindingUtil +import android.os.Build +import android.os.Bundle +import android.support.v4.view.MenuItemCompat +import android.support.v7.widget.SearchView +import android.support.v7.widget.Toolbar +import android.text.TextUtils +import android.view.KeyEvent +import android.view.Menu +import android.view.MenuItem +import android.view.View +import android.view.inputmethod.EditorInfo +import android.webkit.WebResourceRequest +import android.webkit.WebSettings +import android.webkit.WebView +import android.webkit.WebViewClient +import com.loopeer.codereaderkt.Navigator +import com.loopeer.codereaderkt.R +import com.loopeer.codereaderkt.databinding.ActivitySimpleWebBinding +import com.loopeer.codereaderkt.utils.DownloadUrlParser + + +class SimpleWebActivity : BaseActivity(), SearchView.OnQueryTextListener { + + lateinit var binding: ActivitySimpleWebBinding + + private lateinit var mSearchView: SearchView + + private lateinit var mUrl: String + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = DataBindingUtil.setContentView(this, R.layout.activity_simple_web) + mToolbar = findViewById(R.id.toolbar) as Toolbar + initWeb() + parseIntent() + } + + private fun initWeb() { + binding.webContent.settings.javaScriptEnabled = true + binding.webContent.settings.domStorageEnabled = true + binding.webContent.settings.setGeolocationEnabled(true) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + binding.webContent.settings.mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW + } + + binding.webContent.webViewClient = object : WebViewClient() { + override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean { + mSearchView.setQuery(url, true) + return true + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean { + mSearchView.setQuery(request.url.toString(), true) + return true + } + } + + binding.webContent.webChromeClient = WebChromeClient() + } + + private fun parseIntent() { + val intent: Intent = intent + mUrl = intent.getStringExtra(Navigator.EXTRA_WEB_URL) + val htmlString = intent.getStringExtra(Navigator.EXTRA_HTML_STRING) + if (htmlString != null) loadData(htmlString) + } + + private fun loadData(htmlString: String) { + binding.webContent.loadData(htmlString, "text/html", "utf-8") + } + + private fun loadUrl(webUrl: String) { + binding.webContent.loadUrl(webUrl) + } + + override fun onCreateOptionsMenu(menu: Menu?): Boolean { + menuInflater.inflate(R.menu.menu_web_input, menu) + val searchManager = getSystemService(Context.SEARCH_SERVICE) as SearchManager + val inputView = menu?.findItem(R.id.action_web_input) + mSearchView = inputView?.actionView as SearchView + mSearchView.setSearchableInfo(searchManager.getSearchableInfo(componentName)) + mSearchView.isIconified = false + mSearchView.setOnQueryTextListener(this) + mSearchView.imeOptions = EditorInfo.IME_ACTION_GO + mSearchView.queryHint = getString(R.string.web_url_input_hint) + mSearchView.maxWidth = Integer.MAX_VALUE + mSearchView.setQuery(mUrl, true) + MenuItemCompat.setOnActionExpandListener(inputView, object : MenuItemCompat.OnActionExpandListener { + override fun onMenuItemActionExpand(item: MenuItem): Boolean { + mSearchView.post { mSearchView.setQuery(mUrl, false) } + return true + } + + override fun onMenuItemActionCollapse(item: MenuItem): Boolean = true + }) + menuInflater.inflate(R.menu.menu_web_save, menu) + menuInflater.inflate(R.menu.menu_web_actions, menu) + return true + } + + override fun onOptionsItemSelected(item: MenuItem): Boolean { + val id = item.itemId + if (id == R.id.action_save) { + if (!TextUtils.isEmpty(mUrl) && !DownloadUrlParser.parseGithubUrlAndDownload(this@SimpleWebActivity, mUrl)) { + showMessage(getString(R.string.repo_download_url_parse_error)) + } + return true + } + if (id == R.id.menu_action_open_by_browser) { + Navigator().startOutWebActivity(this, mUrl) + return true + } + return super.onOptionsItemSelected(item) + } + + override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean { + if (event.action == KeyEvent.ACTION_DOWN) { + when (keyCode) { + KeyEvent.KEYCODE_BACK -> { + if (binding.webContent!!.canGoBack()) { + binding.webContent!!.goBack() + } else { + finish() + } + return true + } + } + } + return super.onKeyDown(keyCode, event) + } + + override fun onQueryTextSubmit(query: String): Boolean { + if (!TextUtils.isEmpty(query)) { + mUrl = query + loadUrl(mUrl) + mSearchView.clearFocus() + } + return false + } + + override fun onQueryTextChange(newText: String): Boolean = false + + inner class WebChromeClient : android.webkit.WebChromeClient() { + override fun onProgressChanged(view: WebView, newProgress: Int) { + if (newProgress == 100) { + binding.progressBarWeb.visibility = View.GONE + } else { + if (binding.progressBarWeb.visibility == View.GONE) + binding.progressBarWeb.visibility = View.VISIBLE + binding.progressBarWeb.progress = newProgress + } + super.onProgressChanged(view, newProgress) + } + + } + + override fun onDestroy() { + super.onDestroy() + binding.webContent!!.destroy() + } + + companion object { + private val TAG = "SimpleWebActivity" + } +} \ No newline at end of file diff --git a/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/adapter/DirectoryAdapter.kt b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/adapter/DirectoryAdapter.kt new file mode 100644 index 0000000..e290ba9 --- /dev/null +++ b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/adapter/DirectoryAdapter.kt @@ -0,0 +1,161 @@ +package com.loopeer.codereaderkt.ui.adapter + +import android.content.Context +import android.databinding.DataBindingUtil +import android.support.v4.content.ContextCompat +import android.support.v7.widget.RecyclerView +import android.view.View +import android.view.ViewGroup +import com.loopeer.codereaderkt.R +import com.loopeer.codereaderkt.databinding.ListItemCodeReadRepoHeaderBinding +import com.loopeer.codereaderkt.databinding.ListItemDirectoryBinding +import com.loopeer.codereaderkt.model.DirectoryNode +import com.loopeer.codereaderkt.ui.view.DirectoryNavDelegate +import java.util.* + + +class DirectoryAdapter(context: Context, private val mFileClickListener: DirectoryNavDelegate.FileClickListener) : RecyclerViewAdapters(context) { + + private lateinit var mNodeRoot: DirectoryNode + + var nodeRoot: DirectoryNode + get() = mNodeRoot + set(root) { + mNodeRoot = root + updateData(adaptNodes()) + } + + private fun adaptNodes(): List { + val nodes = ArrayList() + if (mNodeRoot.isDirectory) { + val sb = StringBuilder() + createShowNodes(nodes, -1, mNodeRoot, sb) + } else { + mNodeRoot.displayName = mNodeRoot.name + nodes.add(mNodeRoot) + } + return nodes + } + + private fun createShowNodes(nodes: ArrayList, i: Int, nodeRoot: DirectoryNode, sb: StringBuilder) { + var i = i + ++i + if (!nodeRoot.pathNodes?.isEmpty()!!) { + if (nodes.size != 0 && nodeRoot.pathNodes?.size == 1 && nodeRoot.pathNodes!![0].isDirectory) { + nodeRoot.openChild = true + nodes.removeAt(nodes.size - 1) + --i + val node = nodeRoot.pathNodes?.get(0) + node?.depth = i + sb.append(".") + sb.append(node?.name) + node?.displayName = sb.toString() + node?.let { nodes.add(it) } + if (node!!.openChild || node.pathNodes != null + && node.pathNodes?.size == 1 + && node.pathNodes!![0].isDirectory) { + createShowNodes(nodes, i, node, sb) + } + } else { + for (node in nodeRoot.pathNodes!!) { + if (sb.length > 0) sb.delete(0, sb.length) + node.depth = i + sb.append(node.name) + node.displayName = sb.toString() + nodes.add(node) + if (node.openChild || node.pathNodes != null + && node.pathNodes?.size == 1 + && node.pathNodes!![0].isDirectory) { + createShowNodes(nodes, i, node, sb) + } + } + } + } + } + + override fun bindView(var1: DirectoryNode, var2: Int, var3: RecyclerView.ViewHolder?) { + if (var3 is DirectoryViewHolder) { + var3.bind(var1) + val clickListener = View.OnClickListener{ + if (var1.isDirectory) { + var1.openChild = !var1.openChild + updateData(adaptNodes()) + } else { + mFileClickListener.doOpenFile(var1) + } + } + var3.mBinding.root.setOnClickListener(clickListener) + } + (var3 as? CodeReadRepoHeaderViewHolder)?.bind(mNodeRoot) + + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { + val inflater = layoutInflater + when (viewType) { + R.layout.list_item_code_read_repo_header -> { + var mBingding=DataBindingUtil.inflate(inflater,R.layout.list_item_code_read_repo_header, parent, false) + return CodeReadRepoHeaderViewHolder(mBingding) + } + else -> { + var mBingding=DataBindingUtil.inflate(inflater,R.layout.list_item_directory, parent, false) + return DirectoryViewHolder(mBingding) + } + } + } + + override fun getItemViewType(position: Int): Int { + return if (position == 0) { + R.layout.list_item_code_read_repo_header + } else R.layout.list_item_directory + } + + override fun getItemCount(): Int = + if (super.getItemCount() == 0) 0 else super.getItemCount() + 1 + + override fun getItem(position: Int): DirectoryNode? = + if (position == 0) mNodeRoot else super.getItem(position - 1) + + internal inner class DirectoryViewHolder(var mBinding: ListItemDirectoryBinding) : RecyclerView.ViewHolder(mBinding.root) { + + lateinit var mNode: DirectoryNode + + fun bind(pathNode: DirectoryNode) { + mNode = pathNode + mBinding.textDirectoryName.text = pathNode.displayName + val params = itemView.layoutParams as RecyclerView.LayoutParams + params.leftMargin = 20 * pathNode.depth + mBinding.imgDirectoryOpenClose.isSelected = mNode.openChild + var drawableId = R.drawable.ic_directory_file + if (pathNode.isDirectory) { + drawableId = R.drawable.ic_directory_path + mBinding.imgDirectoryOpenClose!!.visibility = if (pathNode.pathNodes?.isEmpty()!!) View.INVISIBLE else View.VISIBLE + } else { + mBinding.imgDirectoryOpenClose.visibility = View.INVISIBLE + } + val drawable = ContextCompat.getDrawable(itemView.context, drawableId) + mBinding.textDirectoryName.setCompoundDrawablesWithIntrinsicBounds(drawable, null, null, null) + } + + } + + + + + + internal inner class CodeReadRepoHeaderViewHolder(mBinding: ListItemCodeReadRepoHeaderBinding) : RecyclerView.ViewHolder(mBinding.root){ + + var mBinding:ListItemCodeReadRepoHeaderBinding + init { + this.mBinding=mBinding + } + + fun bind(directoryNode: DirectoryNode) { + mBinding.imgCodeReadRepoType?.setImageResource(if (directoryNode.isDirectory) R.drawable.ic_repo_white else R.drawable.ic_document_white) + mBinding.textCodeReadRepoName?.text = directoryNode.name + + } + } +} + + diff --git a/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/adapter/ItemTouchHelperCallback.kt b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/adapter/ItemTouchHelperCallback.kt new file mode 100644 index 0000000..15f5859 --- /dev/null +++ b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/adapter/ItemTouchHelperCallback.kt @@ -0,0 +1,27 @@ +package com.loopeer.codereaderkt.ui.adapter + +import android.graphics.Canvas +import android.support.v7.widget.RecyclerView +import android.support.v7.widget.helper.ItemTouchHelper +import com.loopeer.itemtouchhelperextension.ItemTouchHelperExtension + + +class ItemTouchHelperCallback : ItemTouchHelperExtension.Callback() { + + override fun getMovementFlags(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder): Int { + return ItemTouchHelperExtension.Callback.makeMovementFlags(0, ItemTouchHelper.START) + } + + override fun onMove(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean { + return false + } + + override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) { + + } + + override fun onChildDraw(c: Canvas, recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder, dX: Float, dY: Float, actionState: Int, isCurrentlyActive: Boolean) { + if (viewHolder is MainLatestAdapter.RepoViewHolder) + viewHolder.mProgressRelativeLayout.translationX = dX + } +} diff --git a/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/adapter/MainHeaderAdapter.kt b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/adapter/MainHeaderAdapter.kt new file mode 100644 index 0000000..aa2c0fa --- /dev/null +++ b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/adapter/MainHeaderAdapter.kt @@ -0,0 +1,62 @@ +package com.loopeer.codereaderkt.ui.adapter + +import android.content.Context +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.BaseAdapter +import android.widget.ImageView +import android.widget.TextView +import com.loopeer.codereaderkt.Navigator +import com.loopeer.codereaderkt.R +import com.loopeer.codereaderkt.model.MainHeaderItem + + +class MainHeaderAdapter() : BaseAdapter() { + private var mDatas: ArrayList = ArrayList() + private lateinit var mContext: Context + + constructor(context: Context) : this() { + mContext = context + } + + override fun getCount(): Int = mDatas.size + + override fun getItem(i: Int): Any = mDatas[i] + + override fun getItemId(i: Int): Long = i.toLong() + + override fun getView(i: Int, convertView: View?, viewGroup: ViewGroup): View { + val view = LayoutInflater.from(mContext).inflate(R.layout.grid_item_main_header, viewGroup, false) + bindView(mDatas[i], view) + bindClick(view, mDatas[i], i) + return view + } + + private fun bindClick(view: View, item: MainHeaderItem, i: Int) { + view.setOnClickListener { view1 -> + when (i) { + 0 -> Navigator().startSearchActivity(mContext) + 1 -> Navigator().startWebActivity(mContext, item.link) + } + } + } + + private fun bindView(item: MainHeaderItem, view: View) { + val textView = view.findViewById(R.id.text_grid_item) + val imageView = view.findViewById(R.id.img_grid_item) + + textView.setText(item.name) + imageView.setImageResource(item.icon) + } + + fun updateData(items: List) { + setData(items) + notifyDataSetChanged() + } + + private fun setData(items: List) { + mDatas.clear() + mDatas.addAll(items) + } +} diff --git a/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/adapter/MainLatestAdapter.kt b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/adapter/MainLatestAdapter.kt new file mode 100644 index 0000000..d257760 --- /dev/null +++ b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/adapter/MainLatestAdapter.kt @@ -0,0 +1,179 @@ +package com.loopeer.codereaderkt.ui.adapter + +import android.content.Context +import android.support.v7.widget.RecyclerView +import android.text.format.DateUtils +import android.view.View +import android.view.ViewGroup +import android.widget.GridView +import android.widget.ImageView +import android.widget.TextView +import com.loopeer.codereaderkt.Navigator +import com.loopeer.codereaderkt.R +import com.loopeer.codereaderkt.db.CoReaderDbHelper +import com.loopeer.codereaderkt.event.DownloadProgressEvent +import com.loopeer.codereaderkt.model.MainHeaderItem +import com.loopeer.codereaderkt.model.Repo +import com.loopeer.codereaderkt.ui.view.ForegroundProgressRelativeLayout +import com.loopeer.codereaderkt.utils.RxBus +import com.loopeer.itemtouchhelperextension.Extension +import rx.Subscription +import rx.android.schedulers.AndroidSchedulers +import rx.subscriptions.CompositeSubscription + + +class MainLatestAdapter(context: Context) : RecyclerViewAdapters(context) { + + private val TAG = "MainLatestAdapter" + private var mAllSubscription: CompositeSubscription = CompositeSubscription() + + override fun setData(data: List?) { + val list = ArrayList() + list.add(Repo()) + list.addAll(data!!) + super.setData(list) + } + + override fun bindView(var1: Repo, var2: Int, var3: RecyclerView.ViewHolder?) { + if (var3 is RepoViewHolder) { + val viewHolder: RepoViewHolder = var3 + val subscription = viewHolder.bind(var1) + if (subscription != null) { + mAllSubscription.add(subscription) + } + viewHolder.mProgressRelativeLayout.setOnClickListener { + + if (!var1.isDownloading() && !var1.isUnzip) + Navigator().startCodeReadActivity(context, var1) + } + viewHolder.mActionDeleteView.setOnClickListener { doRepoDelete(var3) } + viewHolder.mActionSyncView.setOnClickListener { Navigator().startDownloadRepoService(context, var1) } + } + if (var3 is MainHeaderHolder) { + val viewHolder = var3 + viewHolder.bind() + } + + + } + + private fun doRepoDelete(var3: RecyclerView.ViewHolder) { + val position = var3.adapterPosition + val repo = mData[position] + CoReaderDbHelper.getInstance(context).deleteRepo(java.lang.Long.parseLong(repo.id)) + if (repo.downloadId > 0) Navigator().startDownloadRepoServiceRemove(context, repo.downloadId) + deleteItem(position) + } + + fun deleteRepo(repo: Repo) { + val index = mData.indexOf(repo) + if (index == -1) return + deleteItem(index) + } + + private fun deleteItem(position: Int) { + mData.removeAt(position) + notifyItemRemoved(position) + } + + fun clearSubscription() { + mAllSubscription.clear() + } + + override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): RecyclerView.ViewHolder { + val inflater = layoutInflater + val view: View + return when (viewType) { + R.layout.list_item_main_top_header -> { + view = inflater.inflate(R.layout.list_item_main_top_header, parent, false) + MainHeaderHolder(view) + } + else -> { + view = inflater.inflate(R.layout.list_item_repo, parent, false) + RepoViewHolder(view) + } + } + } + + override fun getItemViewType(position: Int): Int = + if (position == 0) R.layout.list_item_main_top_header else R.layout.list_item_repo + + class RepoViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView), Extension { + + + var mImgRepoType: ImageView = itemView.findViewById(R.id.img_repo_type) + var mTextRepoName: TextView = itemView.findViewById(R.id.text_repo_name) + var mTextRepoTime: TextView = itemView.findViewById(R.id.text_repo_time) + var mProgressRelativeLayout: ForegroundProgressRelativeLayout = itemView.findViewById(R.id.view_progress_list_repo) + var mActionDeleteView: View = itemView.findViewById(R.id.view_list_repo_action_delete) + var mActionSyncView: View = itemView.findViewById(R.id.view_list_repo_action_update) + var mActionContainer: View = itemView.findViewById(R.id.view_list_repo_action_container) + var mCloud: View = itemView.findViewById(R.id.img_list_repo_cloud) + var mLocalPhone: View = itemView.findViewById(R.id.img_list_repo_phone) + private var mSubscription: Subscription? = null + + fun bind(repo: Repo): Subscription? { + mImgRepoType.setBackgroundResource(if (repo.isFolder) R.drawable.shape_circle_folder else R.drawable.shape_circle_document) + mImgRepoType.setImageResource(if (repo.isFolder) R.drawable.ic_repo_white else R.drawable.ic_document_white) + mTextRepoName.text = repo.name + mTextRepoTime.text = DateUtils.getRelativeTimeSpanString(itemView.context, repo.lastModify) + mActionSyncView.visibility = if (repo.isNetRepo()) View.VISIBLE else View.GONE + mCloud.visibility = if (repo.isNetRepo()) View.VISIBLE else View.GONE + mLocalPhone.visibility = if (repo.isLocalRepo()) View.VISIBLE else View.GONE + resetSubscription(repo) + + if (repo.isDownloading()) { + mProgressRelativeLayout.setInitProgress(repo.factor) + } else { + mProgressRelativeLayout.setInitProgress(1f) + } + mProgressRelativeLayout.setUnzip(repo.isUnzip) + + return mSubscription + } + + private fun resetSubscription(repo: Repo) { + if (mSubscription != null && !mSubscription!!.isUnsubscribed) { + mSubscription?.unsubscribe() + } + mSubscription = RxBus.instance + ?.toObservable() + ?.filter({ o -> o is DownloadProgressEvent }) + ?.map({ o -> o as DownloadProgressEvent }) + ?.filter({ o -> o.downloadId == repo.downloadId || repo.id == o.repoId }) + ?.observeOn(AndroidSchedulers.mainThread()) + ?.doOnNext({ o -> if (repo.downloadId == 0L) repo.downloadId = o.downloadId }) + ?.doOnNext({ o -> mProgressRelativeLayout.setProgressCurrent(o.factor) }) + ?.filter({ o -> o.factor == 1f }) + ?.doOnNext({ o -> repo.isUnzip = o.isUnzip }) + ?.doOnNext({ o -> mProgressRelativeLayout.setUnzip(o.isUnzip) }) + ?.filter({ o -> o.isUnzip == false }) + ?.doOnNext({ repo.downloadId = 0 }) + ?.subscribe() + } + + override fun getActionWidth(): Float = mActionContainer.width.toFloat() + } + + + class MainHeaderHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + + private var mMainHeaderAdapter: MainHeaderAdapter = MainHeaderAdapter(itemView.context) + private var mGridView: GridView = itemView.findViewById(R.id.grid_main) + + init { + mGridView.adapter = mMainHeaderAdapter + } + + fun bind() { + val items = ArrayList() + items.add(MainHeaderItem(R.drawable.ic_github, R.string.header_item_github_search, + itemView.context.getString(R.string.header_item_github_search_link))) + items.add(MainHeaderItem(R.drawable.ic_trending, R.string.header_item_trending + , itemView.context.getString(R.string.header_item_trending_link))) + mMainHeaderAdapter.updateData(items) + } + + } + +} \ No newline at end of file diff --git a/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/adapter/RecyclerViewAdapters.kt b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/adapter/RecyclerViewAdapters.kt new file mode 100644 index 0000000..4dedc11 --- /dev/null +++ b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/adapter/RecyclerViewAdapters.kt @@ -0,0 +1,37 @@ +package com.loopeer.codereaderkt.ui.adapter + +import android.content.Context +import android.support.v7.widget.RecyclerView +import android.view.LayoutInflater +import java.util.* + + +abstract class RecyclerViewAdapters(val context: Context) : RecyclerView.Adapter() { + val layoutInflater: LayoutInflater = LayoutInflater.from(context) + var mData: MutableList = ArrayList() + + fun updateData(data: List) { + this.setData(data) + this.notifyDataSetChanged() + } + + open fun setData(data: List?) { + this.mData.clear() + if (data != null) { + this.mData.addAll(data) + } + } + + override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { + val data = this.getItem(position) + if (data != null) { + this.bindView(data, position, holder) + } + } + + abstract fun bindView(var1: T, var2: Int, var3: RecyclerView.ViewHolder?) + + open fun getItem(position: Int): T ? = this.mData[position] + + override fun getItemCount(): Int = this.mData.size +} \ No newline at end of file diff --git a/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/adapter/RepositoryAdapter.kt b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/adapter/RepositoryAdapter.kt new file mode 100644 index 0000000..eb51ecc --- /dev/null +++ b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/adapter/RepositoryAdapter.kt @@ -0,0 +1,74 @@ +package com.loopeer.codereaderkt.ui.adapter + +import android.content.Context +import android.support.v7.widget.RecyclerView +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ImageView +import android.widget.TextView +import com.bumptech.glide.Glide +import com.loopeer.codereaderkt.Navigator +import com.loopeer.codereaderkt.R +import com.loopeer.codereaderkt.model.Repository + + +class RepositoryAdapter(context: Context) : RecyclerViewAdapters(context) { + + private var mHasMore: Boolean = false + + fun setHasMore(hasMore: Boolean) { + mHasMore = hasMore + } + + override fun bindView(var1: Repository, var2: Int, var3: RecyclerView.ViewHolder?) { + (var3 as? RepositoryViewHolder)?.bind(var1, var2) + } + + override fun getItem(position: Int): Repository? = + if (isFooterPositon(position)) null else super.getItem(position) + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder = + when (viewType) { + R.layout.view_footer_loading -> { + val view = LayoutInflater.from(context).inflate(R.layout.view_footer_loading, parent, false) + object : RecyclerView.ViewHolder(view) { + + } + } + else -> { + val view = LayoutInflater.from(context).inflate(R.layout.list_item_repository, parent, false) + RepositoryViewHolder(view) + } + } + + private fun isFooterPositon(position: Int): Boolean = + mHasMore && position == itemCount - 1 + + override fun getItemViewType(position: Int): Int = + if (isFooterPositon(position)) R.layout.view_footer_loading else R.layout.list_item_repository + + override fun getItemCount(): Int = super.getItemCount() + if (mHasMore) 1 else 0 + + internal inner class RepositoryViewHolder(view: View) : RecyclerView.ViewHolder(view) { + private var mImgAvatar: ImageView + private var mTxtFullName: TextView + private var mTxtDescription: TextView + + init { + mImgAvatar = view.findViewById(R.id.img_avatar) + mTxtFullName = view.findViewById(R.id.txt_full_name) + mTxtDescription = view.findViewById(R.id.txt_description) + } + + fun bind(repository: Repository, position: Int) { + Glide.with(context).load(repository.owner?.avatarUrl).into(mImgAvatar) + mTxtFullName.text = repository.fullName + mTxtDescription.text = repository.description + + itemView.setOnClickListener { view -> + Navigator().startWebActivity(context, repository.htmlUrl!!) + } + } + } +} \ No newline at end of file diff --git a/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/decoration/AppbarBehavior.kt b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/decoration/AppbarBehavior.kt new file mode 100644 index 0000000..1018140 --- /dev/null +++ b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/decoration/AppbarBehavior.kt @@ -0,0 +1,22 @@ +package com.loopeer.codereaderkt.ui.decoration + +import android.content.Context +import android.support.design.widget.AppBarLayout +import android.support.design.widget.CoordinatorLayout +import android.util.AttributeSet +import android.view.View + + +class AppbarBehavior : AppBarLayout.Behavior { + + constructor() {} + + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) {} + + override fun onStartNestedScroll(parent: CoordinatorLayout?, child: AppBarLayout?, directTargetChild: View?, target: View?, nestedScrollAxes: Int): Boolean { + var result = super.onStartNestedScroll(parent, child, directTargetChild, target, nestedScrollAxes) + if (result && !target!!.canScrollVertically(1) && child!!.y >= 0) + result = false + return result + } +} \ No newline at end of file diff --git a/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/decoration/DividerItemDecoration.kt b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/decoration/DividerItemDecoration.kt new file mode 100644 index 0000000..1f77020 --- /dev/null +++ b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/decoration/DividerItemDecoration.kt @@ -0,0 +1,158 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.loopeer.codereaderkt.ui.decoration + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Paint +import android.graphics.Rect +import android.support.v4.content.ContextCompat +import android.support.v4.view.ViewCompat +import android.support.v7.widget.LinearLayoutManager +import android.support.v7.widget.RecyclerView +import android.view.View + +import com.loopeer.codereaderkt.R + +open class DividerItemDecoration : RecyclerView.ItemDecoration { + + protected var mOrientation: Int = 0 + protected var padding: Int = 0 + protected var startpadding: Int = 0 + protected var endpadding: Int = 0 + protected var dividerHeight: Int = 0 + protected var mContext: Context + protected lateinit var mPaddingPaint: Paint + protected lateinit var mDividerPaint: Paint + + + @JvmOverloads constructor(context: Context, orientation: Int = VERTICAL_LIST, padding: Int = -1, dividerHeight: Int = -1) { + setOrientation(orientation) + mContext = context + + init() + if (padding != -1) this.padding = padding + updatePaddint() + if (dividerHeight != -1) this.dividerHeight = dividerHeight + } + + constructor(context: Context, orientation: Int, startpadding: Int, endpadding: Int, dividerHeight: Int) { + setOrientation(orientation) + mContext = context + + init() + if (startpadding != -1) this.startpadding = startpadding + if (endpadding != -1) this.endpadding = endpadding + if (dividerHeight != -1) this.dividerHeight = dividerHeight + } + + private fun updatePaddint() { + startpadding = padding + endpadding = padding + } + + private fun init() { + padding = mContext.resources.getDimensionPixelSize(R.dimen.medium_padding) + updatePaddint() + dividerHeight = DEFAULT_DIVIDER_HEIGHT + + mPaddingPaint = Paint(Paint.ANTI_ALIAS_FLAG) + mPaddingPaint.color = ContextCompat.getColor(mContext, R.color.item_background) + mPaddingPaint.style = Paint.Style.FILL + + mDividerPaint = Paint(Paint.ANTI_ALIAS_FLAG) + mDividerPaint.color = ContextCompat.getColor(mContext, R.color.color_divider) + mDividerPaint.style = Paint.Style.FILL + } + + fun setOrientation(orientation: Int) { + if (orientation != HORIZONTAL_LIST && orientation != VERTICAL_LIST) { + throw IllegalArgumentException("invalid orientation") + } + mOrientation = orientation + } + + override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State?) { + super.onDraw(c, parent, state) + if (mOrientation == VERTICAL_LIST) { + drawVertical(c, parent) + } else { + drawHorizontal(c, parent) + } + } + + open fun drawVertical(c: Canvas, parent: RecyclerView) { + val left = parent.paddingLeft + val right = parent.width - parent.paddingRight + + val childCount = parent.childCount + for (i in 0..childCount - 1 - 1) { + val child = parent.getChildAt(i) + val params = child.layoutParams as RecyclerView.LayoutParams + val top = child.bottom + params.bottomMargin + + Math.round(ViewCompat.getTranslationY(child)) + val bottom = top + dividerHeight + + c.drawRect(left.toFloat(), top.toFloat(), (left + startpadding).toFloat(), bottom.toFloat(), mPaddingPaint) + c.drawRect((right - endpadding).toFloat(), top.toFloat(), right.toFloat(), bottom.toFloat(), mPaddingPaint) + c.drawRect((left + startpadding).toFloat(), top.toFloat(), (right - endpadding).toFloat(), bottom.toFloat(), mDividerPaint) + } + } + + fun drawHorizontal(c: Canvas, parent: RecyclerView) { + val top = parent.paddingTop + val bottom = parent.height - parent.paddingBottom + + val childCount = parent.childCount + for (i in 0..childCount - 1 - 1) { + val child = parent.getChildAt(i) + val params = child.layoutParams as RecyclerView.LayoutParams + val left = child.right + params.rightMargin + + Math.round(ViewCompat.getTranslationX(child)) + val right = left + dividerHeight + c.drawRect(left.toFloat(), top.toFloat(), right.toFloat(), (top + startpadding).toFloat(), mPaddingPaint) + c.drawRect(left.toFloat(), (bottom - endpadding).toFloat(), right.toFloat(), bottom.toFloat(), mPaddingPaint) + c.drawRect(left.toFloat(), (top + startpadding).toFloat(), right.toFloat(), (bottom - endpadding).toFloat(), mDividerPaint) + } + } + + override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State?) { + super.getItemOffsets(outRect, view, parent, state) + if (mOrientation == VERTICAL_LIST) { + if (parent.getChildAdapterPosition(view) != parent.adapter.itemCount - 1) { + outRect.set(0, 0, 0, dividerHeight) + } else { + outRect.set(0, 0, 0, 0) + } + } else { + if (parent.getChildAdapterPosition(view) != parent.adapter.itemCount - 1) { + outRect.set(0, 0, dividerHeight, 0) + } else { + outRect.set(0, 0, 0, 0) + } + } + + } + + companion object { + private val DEFAULT_DIVIDER_HEIGHT = 1 + + val HORIZONTAL_LIST = LinearLayoutManager.HORIZONTAL + + val VERTICAL_LIST = LinearLayoutManager.VERTICAL + } +} diff --git a/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/decoration/DividerItemDecorationMainList.kt b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/decoration/DividerItemDecorationMainList.kt new file mode 100644 index 0000000..6b4ead2 --- /dev/null +++ b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/decoration/DividerItemDecorationMainList.kt @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2014 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.loopeer.codereaderkt.ui.decoration + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Rect +import android.support.v4.view.ViewCompat +import android.support.v7.widget.RecyclerView +import android.view.View + +import com.loopeer.codereaderkt.R + +class DividerItemDecorationMainList : DividerItemDecoration { + constructor(context: Context) : super(context) {} + + constructor(context: Context, orientation: Int) : super(context, orientation) {} + + constructor(context: Context, orientation: Int, padding: Int, dividerHeight: Int) : super(context, orientation, padding, dividerHeight) {} + + constructor(context: Context, orientation: Int, startpadding: Int, endpadding: Int, dividerHeight: Int) : super(context, orientation, startpadding, endpadding, dividerHeight) {} + + override fun drawVertical(c: Canvas, parent: RecyclerView) { + val left = parent.paddingLeft + val right = parent.width - parent.paddingRight + + val childCount = parent.childCount + + for (i in 0..childCount - 1 - 1) { + if (i == 0) { + continue + } + val child = parent.getChildAt(i) + val params = child.layoutParams as RecyclerView.LayoutParams + val top = child.bottom + params.bottomMargin + + Math.round(ViewCompat.getTranslationY(child)) + val bottom = top + dividerHeight + + c.drawRect(left.toFloat(), top.toFloat(), (left + startpadding).toFloat(), bottom.toFloat(), mPaddingPaint!!) + c.drawRect((right - endpadding).toFloat(), top.toFloat(), right.toFloat(), bottom.toFloat(), mPaddingPaint!!) + c.drawRect((left + startpadding).toFloat(), top.toFloat(), (right - endpadding).toFloat(), bottom.toFloat(), mDividerPaint!!) + } + } + + override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State?) { + super.getItemOffsets(outRect, view, parent, state) + if (mOrientation == DividerItemDecoration.VERTICAL_LIST) { + if (parent.getChildAdapterPosition(view) == 0) { + outRect.set(0, 0, 0, mContext.resources.getDimensionPixelSize(R.dimen.medium_padding)) + } else if (parent.getChildAdapterPosition(view) != parent.adapter.itemCount - 1) { + outRect.set(0, 0, 0, dividerHeight) + } else { + outRect.set(0, 0, 0, 0) + } + } else { + if (parent.getChildAdapterPosition(view) != parent.adapter.itemCount - 1) { + outRect.set(0, 0, dividerHeight, 0) + } else { + outRect.set(0, 0, 0, 0) + } + } + + } +} diff --git a/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/fragment/BaseFragment.kt b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/fragment/BaseFragment.kt new file mode 100644 index 0000000..753ab2d --- /dev/null +++ b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/fragment/BaseFragment.kt @@ -0,0 +1,114 @@ +package com.loopeer.codereaderkt.ui.fragment + +import android.support.v4.app.Fragment +import android.os.Bundle +import android.text.TextUtils +import android.view.View +import com.loopeer.codereaderkt.R +import com.loopeer.codereaderkt.ui.view.ProgressLoading +import rx.Subscription +import rx.subscriptions.CompositeSubscription + + +open class BaseFragment : Fragment() { + + val mAllSubscribe: CompositeSubscription = CompositeSubscription() + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + } + + + fun registerSubscription(subscription: Subscription) { + mAllSubscribe.add(subscription) + } + + fun unregisterSubscription(subscription: Subscription) { + mAllSubscribe.remove(subscription) + } + + fun clearSubscription() { + mAllSubscribe.clear() + } + + override fun onDestroyView() { + super.onDestroyView() + clearSubscription() + if (isProgressShow() && mProgressLoading == null) { + dismissProgressLoading() + mProgressLoading = null + } + } + + private var mProgressLoading: ProgressLoading? = null + private var mUnBackProgressLoading: ProgressLoading? = null + private var progressShow: Boolean = false + + fun getProgressLoaing(): ProgressLoading? = mProgressLoading + + fun showProgressLoading(resId: Int): Unit { + showProgressLoading(getString(resId)) + } + + open fun showProgressLoading(message: String) { + if (mProgressLoading == null) { + mProgressLoading = ProgressLoading(activity, R.style.ProgressLoadingTheme) + mProgressLoading!!.setCanceledOnTouchOutside(true) + mProgressLoading!!.setOnCancelListener { progressShow = false } + } + if (!TextUtils.isEmpty(message)) { + mProgressLoading!!.setMessage(message) + } else { + mProgressLoading!!.setMessage("") + } + progressShow = true + mProgressLoading!!.show() + } + + fun isProgressShow(): Boolean = progressShow + + open fun dismissProgressLoading() { + if (mProgressLoading != null && isVisible) { + progressShow = false + mProgressLoading!!.dismiss() + } + } + + fun showUnBackProgressLoading(resId: Int) { + showUnBackProgressLoading(getString(resId)) + } + + fun showUnBackProgressLoading(message: String) { + if (mUnBackProgressLoading == null) { + mUnBackProgressLoading = object : ProgressLoading(activity, R.style.ProgressLoadingTheme) { + override fun onBackPressed() { + super.onBackPressed() + } + } + } + if (!TextUtils.isEmpty(message)) { + mUnBackProgressLoading!!.setMessage(message) + } else { + mUnBackProgressLoading!!.setMessage("") + } + mUnBackProgressLoading!!.show() + } + + fun dismissUnBackProgressLoading() { + if (mUnBackProgressLoading != null && isVisible) { + mUnBackProgressLoading!!.dismiss() + } + } + +} + + + + + + + + + + + diff --git a/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/fragment/BaseFullscreenFragment.kt b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/fragment/BaseFullscreenFragment.kt new file mode 100644 index 0000000..ccf2b72 --- /dev/null +++ b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/fragment/BaseFullscreenFragment.kt @@ -0,0 +1,67 @@ +package com.loopeer.codereaderkt.ui.fragment + +import android.os.Build +import android.os.Bundle +import android.os.Handler +import android.support.annotation.RequiresApi +import android.view.View + + +open class BaseFullscreenFragment : BaseFragment() { + private val AUTO_HIDE = true + private val AUTO_HIDE_DELAY_MILLIS = 3000 + private val UI_ANIMATION_DELAY = 300 + + private lateinit var mDecorView: View + + private var mHideHandler = Handler() + + @RequiresApi(Build.VERSION_CODES.JELLY_BEAN) + private val mHideRunnable = Runnable { + -> + run { + mDecorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LOW_PROFILE or View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION + } + + } + + private var mVisible = false + + protected val mDelayHideTouchListener = { -> + if (AUTO_HIDE) { + delayedHide(AUTO_HIDE_DELAY_MILLIS) + } + false + } + + override fun onActivityCreated(savedInstanceState: Bundle?) { + super.onActivityCreated(savedInstanceState) + mDecorView = activity.window.decorView + mVisible = true + } + + private fun toggle() { + if (mVisible) { + hide() + } else { + show() + } + } + + fun hide() { + mVisible = false + mHideHandler.postDelayed(mHideRunnable, UI_ANIMATION_DELAY.toLong()) + } + + protected fun show() { + mDecorView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION + mVisible = true + + mHideHandler.removeCallbacks(mHideRunnable) + } + + protected fun delayedHide(delayMillis: Int) { + mHideHandler.removeCallbacks(mHideRunnable) + mHideHandler.postDelayed(mHideRunnable, delayMillis.toLong()) + } +} \ No newline at end of file diff --git a/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/fragment/CodeReadFragment.kt b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/fragment/CodeReadFragment.kt new file mode 100644 index 0000000..ef7adad --- /dev/null +++ b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/fragment/CodeReadFragment.kt @@ -0,0 +1,331 @@ +package com.loopeer.codereaderkt.ui.fragment + +import android.annotation.TargetApi +import android.content.res.Configuration +import android.databinding.DataBindingUtil +import android.graphics.Bitmap +import android.os.Build +import android.os.Bundle +import android.support.annotation.RequiresApi +import android.support.design.widget.AppBarLayout +import android.support.v4.content.ContextCompat +import android.support.v7.app.AppCompatActivity +import android.support.v7.widget.Toolbar +import android.text.TextUtils +import android.util.Log +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.webkit.WebChromeClient +import android.webkit.WebResourceRequest +import android.webkit.WebView +import android.webkit.WebViewClient +import com.loopeer.codereaderkt.Navigator +import com.loopeer.codereaderkt.R +import com.loopeer.codereaderkt.databinding.FragmentCodeReadBinding +import com.loopeer.codereaderkt.model.DirectoryNode +import com.loopeer.codereaderkt.ui.loader.CodeFragmentContentLoader +import com.loopeer.codereaderkt.ui.loader.ILoadHelper +import com.loopeer.codereaderkt.ui.view.NestedScrollWebView +import com.loopeer.codereaderkt.utils.* +import com.todou.markdownj.MarkdownProcessor +import rx.Observable +import rx.Subscription +import rx.android.schedulers.AndroidSchedulers +import rx.schedulers.Schedulers +import java.io.* +import java.util.concurrent.TimeUnit + + +open class CodeReadFragment : BaseFullscreenFragment(), NestedScrollWebView.ScrollChangeListener { + private val TAG = "CodeReadFragment" + + private lateinit var mBinding: FragmentCodeReadBinding + private lateinit var mToolbar: Toolbar + + + private var mNode: DirectoryNode? = null + private var mRootNode: DirectoryNode? = null + private var scrollFinishDelaySubscription: Subscription? = null + private var mScrollDown = false + private var mOpenFileAfterLoadFinish = false + private var mCodeContentLoader: ILoadHelper? = null + + private var mOrientationChange: Boolean = false + + + companion object { + fun newInstance(node: DirectoryNode?, root: DirectoryNode?): CodeReadFragment { + val codeReadFragment = CodeReadFragment() + codeReadFragment.mNode = node + codeReadFragment.mRootNode = root + return codeReadFragment + } + } + + override fun onCreateView(inflater: LayoutInflater?, container: ViewGroup?, savedInstanceState: Bundle?): View { + mBinding = DataBindingUtil.inflate(inflater, R.layout.fragment_code_read, container, false) + mToolbar=mBinding.root.findViewById(R.id.toolbar)!! + return mBinding.root + } + + @RequiresApi(Build.VERSION_CODES.M) + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + mCodeContentLoader = CodeFragmentContentLoader(view) + + setupToolbar() + + val activity = activity as AppCompatActivity + activity.setSupportActionBar(mToolbar) + activity.supportActionBar!!.setDisplayHomeAsUpEnabled(true) + activity.supportActionBar!!.setHomeAsUpIndicator(ContextCompat.getDrawable(context, R.drawable.ic_view_list_white)) + mBinding.webCodeRead.setScrollChangeListener(this) + mBinding.webCodeRead.settings.javaScriptEnabled = true + mBinding.webCodeRead.settings.setSupportZoom(true) + mBinding.webCodeRead.settings.builtInZoomControls = true + mBinding.webCodeRead.webViewClient = object : WebViewClient() { + override fun onPageStarted(view: WebView, url: String?, favicon: Bitmap?) { + super.onPageStarted(view, url, favicon) + mCodeContentLoader!!.showContent() + } + + override fun shouldOverrideUrlLoading(view: WebView, url: String?): Boolean { + url?.let { Navigator().startWebActivity(context, it) } + return true + } + + @RequiresApi(Build.VERSION_CODES.M) + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + override fun shouldOverrideUrlLoading(view: WebView, request: WebResourceRequest): Boolean { + Navigator().startWebActivity(context, request.url.toString()) + return true + } + } + + mBinding.webCodeRead.webChromeClient = object : WebChromeClient() { + + } + if (Build.VERSION.SDK_INT >= 11) { + (Runnable { mBinding.webCodeRead.settings.displayZoomControls = false }).run() + } + openFile() + } + + private fun setupToolbar() { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) + return + + val params = mToolbar.layoutParams as AppBarLayout.LayoutParams + params.height = (DeviceUtils.dpToPx(activity, 56f) + DeviceUtils.statusBarHeight).toInt() + mToolbar. + layoutParams = params + mToolbar.setPadding(0, DeviceUtils.statusBarHeight, 0, 0) + } + + + @TargetApi(Build.VERSION_CODES.M) + private fun openFile() { + mCodeContentLoader!!.showProgress() + if (mBinding.webCodeRead == null) { + return + } + mBinding.webCodeRead.clearHistory() + if (mNode == null) { + if (mOpenFileAfterLoadFinish) + mCodeContentLoader!!.showEmpty(getString(R.string.code_read_no_file_open)) + } else if (FileTypeUtils.isImageFileType(mNode!!.absolutePath)) { + openImageFile() + } else if (FileTypeUtils.isMdFileType(mNode!!.absolutePath)) { + openMdShowFile() + } else { + openCodeFile() + } + } + + @RequiresApi(Build.VERSION_CODES.M) + private fun openImageFile() { + val string = "" + + "" + "" + "" + mBinding.webCodeRead.loadDataWithBaseURL(null, string, "text/html", "utf-8", null) + } + + + + fun openFile(node: DirectoryNode) { + mOpenFileAfterLoadFinish = true + mNode = node + if (!isVisible) return + openFile() + } + + private fun openCodeFile() { + Observable.create(Observable.OnSubscribe { subscriber -> + var stream: InputStream? = null + try { + stream = FileInputStream(mNode!!.absolutePath) + } catch (e: FileNotFoundException) { + e.printStackTrace() + } + + if (stream == null) { + subscriber.onCompleted() + return@OnSubscribe + } + val finalStream = stream + val names = mNode?.name?.split("\\.".toRegex())?.dropLastWhile { it.isEmpty() }?.toTypedArray() + var jsFile = BrushMap.getJsFileForExtension(names?.get(names.size - 1)) + if (jsFile == null) { + jsFile = "Txt" + } + val sb = StringBuilder() + val localStringBuilder = StringBuilder() + var localBufferedReader:BufferedReader?=null + try { + localBufferedReader = BufferedReader( + InputStreamReader(finalStream, "UTF-8")) + var str:String? + while (true) { + str = localBufferedReader.readLine() + if(str!=null){ + localStringBuilder.append(str) + localStringBuilder.append("\n") + }else{ + break + } + } + sb.append("
")
+                sb.append(TextUtils.htmlEncode(localStringBuilder.toString()))
+                sb.append("
") + subscriber.onNext(HtmlParser.buildHtmlContent(activity, sb.toString(), jsFile, mNode!!.name)) + } catch (e: OutOfMemoryError) { + subscriber.onError(e) + } catch (e: FileNotFoundException) { + subscriber.onError(e) + } + finally { + localBufferedReader?.close() + } + subscriber.onCompleted() + }) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .doOnNext { o -> mBinding.webCodeRead.loadDataWithBaseURL("file:///android_asset/", o, "text/html", "UTF-8", "") } + + .onErrorResumeNext(Observable.empty()) + .subscribe() + } + + + protected fun openMdShowFile() { + registerSubscription( + Observable.create(Observable.OnSubscribe { subscriber -> + var stream: InputStream? = null + try { + stream = FileInputStream(mNode!!.absolutePath) + } catch (e: FileNotFoundException) { + subscriber.onError(e) + } + + if (stream == null) + return@OnSubscribe + val finalStream = stream + val localStringBuilder = StringBuilder() + try { + val localBufferedReader = BufferedReader( + InputStreamReader(finalStream, "UTF-8")) + while (true) { + val str = localBufferedReader.readLine() ?: break + localStringBuilder.append(str) + localStringBuilder.append("\n") + } + val textString = localStringBuilder.toString() + + if (textString != null) { + val m = MarkdownProcessor(mRootNode!!.absolutePath) + m.setTextColorString(ColorUtils.getColorString(context, R.color.text_color_primary)) + m.setBackgroundColorString(ColorUtils.getColorString(context, R.color.code_read_background_color)) + m.setCodeBlockColor(ColorUtils.getColorString(context, R.color.code_block_color)) + m.setTableBorderColor(ColorUtils.getColorString(context, R.color.table_block_border_color)) + val html = m.markdown(textString) + subscriber.onNext(html) + } + subscriber.onCompleted() + } catch (e: OutOfMemoryError) { + subscriber.onError(e) + } catch (e: FileNotFoundException) { + subscriber.onError(e) + } + }) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .doOnNext { s -> mBinding.webCodeRead.loadDataWithBaseURL("fake://", s, "text/html", "UTF-8", "") } + .doOnError { e -> + mCodeContentLoader?.showEmpty(e.message!!) + } + + .onErrorResumeNext(Observable.empty()) + .subscribe() + ) + } + + override fun onDestroyView() { + super.onDestroyView() + + mBinding.webCodeRead.destroy() + } + + override fun onConfigurationChanged(newConfig: Configuration?) { + super.onConfigurationChanged(newConfig) + mOrientationChange = true + } + + override fun onScrollChanged(l: Int, t: Int, oldl: Int, oldt: Int) { + if (mOrientationChange) { + mOrientationChange = false + return + } + + if (scrollFinishDelaySubscription != null && !scrollFinishDelaySubscription!!.isUnsubscribed()) { + scrollFinishDelaySubscription!!.unsubscribe() + } + if (t - oldt > 70) { + if (mScrollDown) + return + + mScrollDown = true + } else if (t - oldt < 0) { + if (!mScrollDown) + return + + mScrollDown = false + closeFullScreen() + } + if (mScrollDown) { + scrollFinishDelaySubscription = Observable + .timer(500, TimeUnit.MILLISECONDS) + .observeOn(AndroidSchedulers.mainThread()) + .doOnNext { lo -> openFullScreen() } + .subscribe() + registerSubscription(subscription = scrollFinishDelaySubscription!!) + } + } + + fun updateRootNode(directoryNode: DirectoryNode) { + mRootNode = directoryNode + } + + fun getCodeContentLoader(): ILoadHelper? { + return this.mCodeContentLoader + } + + private fun openFullScreen() { + hide() + } + + private fun closeFullScreen() { + show() + } +} \ No newline at end of file diff --git a/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/loader/CodeFragmentLoader.kt b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/loader/CodeFragmentLoader.kt new file mode 100644 index 0000000..98e479c --- /dev/null +++ b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/loader/CodeFragmentLoader.kt @@ -0,0 +1,41 @@ +package com.loopeer.codereaderkt.ui.loader + +import android.view.View +import android.widget.TextView +import android.widget.ViewAnimator +import com.loopeer.codereaderkt.R +import com.loopeer.codereaderkt.ui.view.ProgressIndicatorView + + +class CodeFragmentContentLoader(contentView: View) : ILoadHelper { + +// @BindView(R.id.progress_code_fragment) + internal var mProgressIndicatorView: ProgressIndicatorView? = null +// @BindView(R.id.content_animator) + internal var mContentAnimator: ViewAnimator? = null +// @BindView(android.R.id.empty) + internal var mTextEmpty: TextView? = null + + init { + mProgressIndicatorView = contentView.findViewById(R.id.progress_code_fragment) + mContentAnimator = contentView.findViewById(R.id.content_animator) + mTextEmpty = contentView.findViewById(android.R.id.empty) +// ButterKnife.bind(this, contentView) + } + + override fun showProgress() { + mContentAnimator!!.displayedChild = 1 + mProgressIndicatorView!!.setAnimationStatus(ProgressIndicatorView.AnimStatus.START) + } + + override fun showContent() { + mContentAnimator!!.displayedChild = 0 + mProgressIndicatorView!!.setAnimationStatus(ProgressIndicatorView.AnimStatus.CANCEL) + } + + override fun showEmpty(message: String) { + mContentAnimator!!.displayedChild = 2 + mTextEmpty!!.text = message + mProgressIndicatorView!!.setAnimationStatus(ProgressIndicatorView.AnimStatus.CANCEL) + } +} \ No newline at end of file diff --git a/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/loader/ILoaderHelper.kt b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/loader/ILoaderHelper.kt new file mode 100644 index 0000000..b7da0f3 --- /dev/null +++ b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/loader/ILoaderHelper.kt @@ -0,0 +1,12 @@ +package com.loopeer.codereaderkt.ui.loader + + +interface ILoadHelper { + + fun showProgress() + + fun showContent() + + fun showEmpty(message: String) + +} \ No newline at end of file diff --git a/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/loader/RecyclerLoader.kt b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/loader/RecyclerLoader.kt new file mode 100644 index 0000000..eb396e0 --- /dev/null +++ b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/loader/RecyclerLoader.kt @@ -0,0 +1,19 @@ +package com.loopeer.codereaderkt.ui.loader + +import android.widget.ViewAnimator + + +class RecyclerLoader(internal var mViewAnimator: ViewAnimator) : ILoadHelper { + + override fun showProgress() { + mViewAnimator.displayedChild = 2 + } + + override fun showContent() { + mViewAnimator.displayedChild = 0 + } + + override fun showEmpty(message: String) { + mViewAnimator.displayedChild = 1 + } +} \ No newline at end of file diff --git a/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/view/AddRepoChecker.kt b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/view/AddRepoChecker.kt new file mode 100644 index 0000000..99a48ce --- /dev/null +++ b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/view/AddRepoChecker.kt @@ -0,0 +1,19 @@ +package com.loopeer.codereaderkt.ui.view + +import android.text.TextUtils + +class AddRepoChecker(checkObserver: Checker.CheckObserver) : Checker(checkObserver) { + override val isEnable: Boolean + get() = !TextUtils.isEmpty(repoDownloadUrl) //To change initializer of created properties use File | Settings | File Templates. + + var repoName:String?=null + set(value) { + field = value + mCheckObserver.check(isEnable) + } + var repoDownloadUrl: String?=null + set(value) { + field = value + mCheckObserver.check(isEnable) + } +} diff --git a/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/view/Checker.kt b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/view/Checker.kt new file mode 100644 index 0000000..1a4c9e4 --- /dev/null +++ b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/view/Checker.kt @@ -0,0 +1,9 @@ +package com.loopeer.codereaderkt.ui.view + +abstract class Checker(internal var mCheckObserver: CheckObserver) { + interface CheckObserver { + fun check(b: Boolean) + } + + internal abstract val isEnable: Boolean +} diff --git a/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/view/DirectoryNavDelegate.kt b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/view/DirectoryNavDelegate.kt new file mode 100644 index 0000000..65ad93c --- /dev/null +++ b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/view/DirectoryNavDelegate.kt @@ -0,0 +1,107 @@ +package com.loopeer.codereaderkt.ui.view + +import android.content.Context +import android.support.v7.widget.LinearLayoutManager +import android.support.v7.widget.RecyclerView +import android.util.Log +import android.widget.Toast +import com.loopeer.codereaderkt.model.DirectoryNode +import com.loopeer.codereaderkt.ui.adapter.DirectoryAdapter +import com.loopeer.codereaderkt.utils.FileCache +import com.loopeer.codereaderkt.utils.FileTypeUtils +import rx.Observable +import rx.android.schedulers.AndroidSchedulers +import rx.observables.AsyncOnSubscribe +import rx.schedulers.Schedulers +import rx.subscriptions.CompositeSubscription +import java.io.File + +class DirectoryNavDelegate(private val mRecyclerView: RecyclerView, private val mFileClickListener: FileClickListener) { + + interface FileClickListener { + fun doOpenFile(node: DirectoryNode?) + } + + interface LoadFileCallback { + fun onFileOpenStart() + fun onFileOpenEnd() + } + + private val mDirectoryAdapter: DirectoryAdapter = DirectoryAdapter(mRecyclerView.context, mFileClickListener) + private val mContext: Context = mRecyclerView.context + private lateinit var mLoadFileCallback: LoadFileCallback + private val mAllSubscription = CompositeSubscription() + + init { + setUpRecyclerView() + } + + fun setLoadFileCallback(loadFileCallback: LoadFileCallback) { + mLoadFileCallback = loadFileCallback + } + + fun clearSubscription() { + mAllSubscription.clear() + } + + fun resumeDirectoryState(node: DirectoryNode) { + mDirectoryAdapter.nodeRoot = node + } + + val directoryNodeInstance: DirectoryNode + get() = mDirectoryAdapter.nodeRoot + + private fun setUpRecyclerView() { + mRecyclerView.layoutManager = LinearLayoutManager(mContext) + mRecyclerView.adapter = mDirectoryAdapter + } + + fun updateData(directoryNode: DirectoryNode) { + mLoadFileCallback.onFileOpenStart() + mAllSubscription.add( + Observable.fromCallable{ + val node: DirectoryNode + var isDirectory=directoryNode.isDirectory + if (isDirectory) { + node = FileCache().getFileDirectory(File(directoryNode.absolutePath!!))!! + } else { + node = directoryNode + } + node + } + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .doOnNext({ mDirectoryAdapter.nodeRoot=it}) + .doOnNext({ checkOpenFirstFile(it) }) + .doOnError { + e -> Toast.makeText(mContext, e.message, Toast.LENGTH_SHORT).show() + } + .onErrorResumeNext(Observable.empty()) + .doOnCompleted { mLoadFileCallback.onFileOpenEnd() } + .subscribe()) + } + + private fun checkOpenFirstFile(node: DirectoryNode) { + if (node.isDirectory) { + var haveOpen = false + for (n in node.pathNodes!!) { + if (FileTypeUtils.isMdFileType(n.name) && n.name.equals("readme.md", ignoreCase = true)) { + mFileClickListener.doOpenFile(n) + haveOpen = true + } + } + if (!haveOpen) { + mFileClickListener.doOpenFile(null) + } + } else if (!node.isDirectory) { + mFileClickListener.doOpenFile(node) + } else { + mFileClickListener.doOpenFile(null) + } + } + + companion object { + private val TAG = "DirectoryNavDelegate" + } + +} diff --git a/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/view/DrawerLayout.kt b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/view/DrawerLayout.kt new file mode 100644 index 0000000..18a7ef3 --- /dev/null +++ b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/view/DrawerLayout.kt @@ -0,0 +1,2255 @@ +package com.loopeer.codereaderkt.ui.view + +import android.annotation.SuppressLint +import android.content.Context +import android.graphics.Canvas +import android.graphics.Paint +import android.graphics.PixelFormat +import android.graphics.Rect +import android.graphics.drawable.ColorDrawable +import android.graphics.drawable.Drawable +import android.os.Build +import android.os.Parcel +import android.os.Parcelable +import android.os.SystemClock +import android.support.annotation.ColorInt +import android.support.annotation.DrawableRes +import android.support.annotation.IntDef +import android.support.annotation.RequiresApi +import android.support.v4.content.ContextCompat +import android.support.v4.graphics.drawable.DrawableCompat +import android.support.v4.os.ParcelableCompat +import android.support.v4.os.ParcelableCompatCreatorCallbacks +import android.support.v4.view.* +import android.support.v4.view.AbsSavedState +import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat +import android.support.v4.widget.ViewDragHelper +import android.util.AttributeSet +import android.view.* +import android.view.accessibility.AccessibilityEvent +import java.lang.annotation.Retention +import java.lang.annotation.RetentionPolicy +import java.util.* + +class DrawerLayout @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0) : ViewGroup(context, attrs, defStyle), DrawerLayoutImpl { + + @IntDef(STATE_IDLE.toLong(), STATE_DRAGGING.toLong(), STATE_SETTLING.toLong()) + @Retention(RetentionPolicy.SOURCE) + private annotation class State + + /** @hide + */ + @IntDef(LOCK_MODE_UNLOCKED.toLong(), LOCK_MODE_LOCKED_CLOSED.toLong(), LOCK_MODE_LOCKED_OPEN.toLong(), LOCK_MODE_UNDEFINED.toLong()) + @Retention(RetentionPolicy.SOURCE) + private annotation class LockMode + + /** @hide + */ + @IntDef(Gravity.LEFT.toLong(), Gravity.RIGHT.toLong(), GravityCompat.START.toLong(), GravityCompat.END.toLong()) + @Retention(RetentionPolicy.SOURCE) + private annotation class EdgeGravity + + private val mChildAccessibilityDelegate = ChildAccessibilityDelegate() + private var mDrawerElevation: Float = 0.toFloat() + + private val mMinDrawerMargin: Int + + private var mScrimColor = DEFAULT_SCRIM_COLOR + private var mScrimOpacity: Float = 0.toFloat() + private val mScrimPaint = Paint() + + private val mLeftDragger: ViewDragHelper + private val mRightDragger: ViewDragHelper + private val mLeftCallback: ViewDragCallback + private val mRightCallback: ViewDragCallback + private var mDrawerState: Int = 0 + private var mInLayout: Boolean = false + private var mFirstLayout = true + + @LockMode private var mLockModeLeft = LOCK_MODE_UNDEFINED + @LockMode private var mLockModeRight = LOCK_MODE_UNDEFINED + @LockMode private var mLockModeStart = LOCK_MODE_UNDEFINED + @LockMode private var mLockModeEnd = LOCK_MODE_UNDEFINED + + private var mDisallowInterceptRequested: Boolean = false + private var mChildrenCanceledTouch: Boolean = false + + private var mListener: DrawerListener? = null + private var mListeners: MutableList? = null + + private var mInitialMotionX: Float = 0.toFloat() + private var mInitialMotionY: Float = 0.toFloat() + + /** + * Gets the drawable used to draw in the insets area for the status bar. + + * @return The status bar background drawable, or null if none set + */ + var statusBarBackgroundDrawable: Drawable? = null + private set + private var mShadowLeftResolved: Drawable? = null + private var mShadowRightResolved: Drawable? = null + + private var mTitleLeft: CharSequence? = null + private var mTitleRight: CharSequence? = null + + private var mLastInsets: Any? = null + private var mDrawStatusBarBackground: Boolean = false + + /** Shadow drawables for different gravity */ + private var mShadowStart: Drawable? = null + private var mShadowEnd: Drawable? = null + private var mShadowLeft: Drawable? = null + private var mShadowRight: Drawable? = null + + private val mNonDrawerViews: ArrayList + + /** + * Listener for monitoring events about drawers. + */ + interface DrawerListener { + /** + * Called when a drawer's position changes. + * @param drawerView The child view that was moved + * * + * @param slideOffset The new offset of this drawer within its range, from 0-1 + */ + fun onDrawerSlide(drawerView: View, slideOffset: Float) + + /** + * Called when a drawer has settled in a completely open state. + * The drawer is interactive at this point. + + * @param drawerView Drawer view that is now open + */ + fun onDrawerOpened(drawerView: View) + + /** + * Called when a drawer has settled in a completely closed state. + + * @param drawerView Drawer view that is now closed + */ + fun onDrawerClosed(drawerView: View) + + /** + * Called when the drawer motion state changes. The new state will + * be one of [.STATE_IDLE], [.STATE_DRAGGING] or [.STATE_SETTLING]. + + * @param newState The new drawer motion state + */ + fun onDrawerStateChanged(@State newState: Int) + } + + /** + * Stub/no-op implementations of all methods of [DrawerListener]. + * Override this if you only care about a few of the available callback methods. + */ + abstract class SimpleDrawerListener : DrawerListener { + override fun onDrawerSlide(drawerView: View, slideOffset: Float) {} + + override fun onDrawerOpened(drawerView: View) {} + + override fun onDrawerClosed(drawerView: View) {} + + override fun onDrawerStateChanged(newState: Int) {} + } + + internal interface DrawerLayoutCompatImpl { + fun configureApplyInsets(drawerLayout: View) + fun dispatchChildInsets(child: View, insets: Any?, drawerGravity: Int) + fun applyMarginInsets(lp: ViewGroup.MarginLayoutParams, insets: Any?, drawerGravity: Int) + fun getTopInset(lastInsets: Any?): Int + fun getDefaultStatusBarBackground(context: Context): Drawable? + } + + internal class DrawerLayoutCompatImplBase : DrawerLayoutCompatImpl { + override fun configureApplyInsets(drawerLayout: View) { + // This space for rent + } + + override fun dispatchChildInsets(child: View, insets: Any?, drawerGravity: Int) { + // This space for rent + } + + override fun applyMarginInsets(lp: ViewGroup.MarginLayoutParams, insets: Any?, drawerGravity: Int) { + // This space for rent + } + + override fun getTopInset(insets: Any?): Int { + return 0 + } + + override fun getDefaultStatusBarBackground(context: Context): Drawable? { + return null + } + } + + internal class DrawerLayoutCompatImplApi21 : DrawerLayoutCompatImpl { + override fun configureApplyInsets(drawerLayout: View) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH) { + DrawerLayoutCompatApi21.configureApplyInsets(drawerLayout) + } + } + + override fun dispatchChildInsets(child: View, insets: Any?, drawerGravity: Int) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH) { + DrawerLayoutCompatApi21.dispatchChildInsets(child, insets!!, drawerGravity) + } + } + + override fun applyMarginInsets(lp: ViewGroup.MarginLayoutParams, insets: Any?, drawerGravity: Int) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT_WATCH) { + DrawerLayoutCompatApi21.applyMarginInsets(lp, insets!!, drawerGravity) + } + } + + @RequiresApi(Build.VERSION_CODES.KITKAT_WATCH) + override fun getTopInset(insets: Any?): Int { + return DrawerLayoutCompatApi21.getTopInset(insets) + } + + override fun getDefaultStatusBarBackground(context: Context): Drawable { + return DrawerLayoutCompatApi21.getDefaultStatusBarBackground(context) + } + } + + init { + descendantFocusability = ViewGroup.FOCUS_AFTER_DESCENDANTS + val density = resources.displayMetrics.density + mMinDrawerMargin = (MIN_DRAWER_MARGIN * density + 0.5f).toInt() + val minVel = MIN_FLING_VELOCITY * density + + mLeftCallback = ViewDragCallback(Gravity.LEFT) + mRightCallback = ViewDragCallback(Gravity.RIGHT) + + mLeftDragger = ViewDragHelper.create(this, TOUCH_SLOP_SENSITIVITY, mLeftCallback) + mLeftDragger.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT) + mLeftDragger.minVelocity = minVel + mLeftCallback.setDragger(mLeftDragger) + + mRightDragger = ViewDragHelper.create(this, TOUCH_SLOP_SENSITIVITY, mRightCallback) + mRightDragger.setEdgeTrackingEnabled(ViewDragHelper.EDGE_RIGHT) + mRightDragger.minVelocity = minVel + mRightCallback.setDragger(mRightDragger) + + // So that we can catch the back button + isFocusableInTouchMode = true + + ViewCompat.setImportantForAccessibility(this, + ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES) + + ViewCompat.setAccessibilityDelegate(this, AccessibilityDelegate()) + ViewGroupCompat.setMotionEventSplittingEnabled(this, false) + if (ViewCompat.getFitsSystemWindows(this)) { + IMPL.configureApplyInsets(this) + statusBarBackgroundDrawable = IMPL.getDefaultStatusBarBackground(context) + } + + mDrawerElevation = DRAWER_ELEVATION * density + + mNonDrawerViews = ArrayList() + } + + /** + * The base elevation of the drawer(s) relative to the parent, in pixels. Note that the + * elevation change is only supported in API 21 and above. For unsupported API levels, 0 will + * be returned as the elevation. + + * @return The base depth position of the view, in pixels. + */ + /** + * Sets the base elevation of the drawer(s) relative to the parent, in pixels. Note that the + * elevation change is only supported in API 21 and above. + + * @param elevation The base depth position of the view, in pixels. + */ + var drawerElevation: Float + get() { + if (SET_DRAWER_SHADOW_FROM_ELEVATION) { + return mDrawerElevation + } + return 0f + } + set(elevation) { + mDrawerElevation = elevation + for (i in 0..childCount - 1) { + val child = getChildAt(i) + if (isDrawerView(child)) { + ViewCompat.setElevation(child, mDrawerElevation) + } + } + } + + /** + * @hide Internal use only; called to apply window insets when configured + * * with fitsSystemWindows="true" + */ + override fun setChildInsets(insets: Any, draw: Boolean) { + mLastInsets = insets + mDrawStatusBarBackground = draw + setWillNotDraw(!draw && background == null) + requestLayout() + } + + /** + * Set a simple drawable used for the left or right shadow. The drawable provided must have a + * nonzero intrinsic width. For API 21 and above, an elevation will be set on the drawer + * instead of the drawable provided. + + * + * Note that for better support for both left-to-right and right-to-left layout + * directions, a drawable for RTL layout (in additional to the one in LTR layout) can be + * defined with a resource qualifier "ldrtl" for API 17 and above with the gravity + * [GravityCompat.START]. Alternatively, for API 23 and above, the drawable can + * auto-mirrored such that the drawable will be mirrored in RTL layout. + + * @param shadowDrawable Shadow drawable to use at the edge of a drawer + * * + * @param gravity Which drawer the shadow should apply to + */ + fun setDrawerShadow(shadowDrawable: Drawable, @EdgeGravity gravity: Int) { + /* + * TODO Someone someday might want to set more complex drawables here. + * They're probably nuts, but we might want to consider registering callbacks, + * setting states, etc. properly. + */ + if (SET_DRAWER_SHADOW_FROM_ELEVATION) { + // No op. Drawer shadow will come from setting an elevation on the drawer. + return + } + if (gravity and GravityCompat.START == GravityCompat.START) { + mShadowStart = shadowDrawable + } else if (gravity and GravityCompat.END == GravityCompat.END) { + mShadowEnd = shadowDrawable + } else if (gravity and Gravity.LEFT == Gravity.LEFT) { + mShadowLeft = shadowDrawable + } else if (gravity and Gravity.RIGHT == Gravity.RIGHT) { + mShadowRight = shadowDrawable + } else { + return + } + resolveShadowDrawables() + invalidate() + } + + /** + * Set a simple drawable used for the left or right shadow. The drawable provided must have a + * nonzero intrinsic width. For API 21 and above, an elevation will be set on the drawer + * instead of the drawable provided. + + * + * Note that for better support for both left-to-right and right-to-left layout + * directions, a drawable for RTL layout (in additional to the one in LTR layout) can be + * defined with a resource qualifier "ldrtl" for API 17 and above with the gravity + * [GravityCompat.START]. Alternatively, for API 23 and above, the drawable can + * auto-mirrored such that the drawable will be mirrored in RTL layout. + + * @param resId Resource id of a shadow drawable to use at the edge of a drawer + * * + * @param gravity Which drawer the shadow should apply to + */ + fun setDrawerShadow(@DrawableRes resId: Int, @EdgeGravity gravity: Int) { + setDrawerShadow(resources.getDrawable(resId), gravity) + } + + /** + * Set a color to use for the scrim that obscures primary content while a drawer is open. + + * @param color Color to use in 0xAARRGGBB format. + */ + fun setScrimColor(@ColorInt color: Int) { + mScrimColor = color + invalidate() + } + + /** + * Set a listener to be notified of drawer events. Note that this method is deprecated + * and you should use [.addDrawerListener] to add a listener and + * [.removeDrawerListener] to remove a registered listener. + + * @param listener Listener to notify when drawer events occur + * * + * @see DrawerListener + + * @see .addDrawerListener + * @see .removeDrawerListener + */ + + @Deprecated("Use {@link #addDrawerListener(DrawerListener)}\n ") + fun setDrawerListener(listener: DrawerListener?) { + // The logic in this method emulates what we had before support for multiple + // registered listeners. + if (mListener != null) { + removeDrawerListener(mListener!!) + } + if (listener != null) { + addDrawerListener(listener) + } + // Update the deprecated field so that we can remove the passed listener the next + // time we're called + mListener = listener + } + + /** + * Adds the specified listener to the list of listeners that will be notified of drawer events. + + * @param listener Listener to notify when drawer events occur. + * * + * @see .removeDrawerListener + */ + fun addDrawerListener(listener: DrawerListener) { + if (listener == null) { + return + } + if (mListeners == null) { + mListeners = ArrayList() + } + mListeners!!.add(listener) + } + + /** + * Removes the specified listener from the list of listeners that will be notified of drawer + * events. + + * @param listener Listener to remove from being notified of drawer events + * * + * @see .addDrawerListener + */ + fun removeDrawerListener(listener: DrawerListener) { + if (listener == null) { + return + } + if (mListeners == null) { + // This can happen if this method is called before the first call to addDrawerListener + return + } + mListeners!!.remove(listener) + } + + /** + * Enable or disable interaction with all drawers. + + * + * This allows the application to restrict the user's ability to open or close + * any drawer within this layout. DrawerLayout will still respond to calls to + * [.openDrawer], [.closeDrawer] and friends if a drawer is locked. + + * + * Locking drawers open or closed will implicitly open or close + * any drawers as appropriate. + + * @param lockMode The new lock mode for the given drawer. One of [.LOCK_MODE_UNLOCKED], + * * [.LOCK_MODE_LOCKED_CLOSED] or [.LOCK_MODE_LOCKED_OPEN]. + */ + fun setDrawerLockMode(@LockMode lockMode: Int) { + setDrawerLockMode(lockMode, Gravity.LEFT) + setDrawerLockMode(lockMode, Gravity.RIGHT) + } + + /** + * Enable or disable interaction with the given drawer. + + * + * This allows the application to restrict the user's ability to open or close + * the given drawer. DrawerLayout will still respond to calls to [.openDrawer], + * [.closeDrawer] and friends if a drawer is locked. + + * + * Locking a drawer open or closed will implicitly open or close + * that drawer as appropriate. + + * @param lockMode The new lock mode for the given drawer. One of [.LOCK_MODE_UNLOCKED], + * * [.LOCK_MODE_LOCKED_CLOSED] or [.LOCK_MODE_LOCKED_OPEN]. + * * + * @param edgeGravity Gravity.LEFT, RIGHT, START or END. + * * Expresses which drawer to change the mode for. + * * + * * + * @see .LOCK_MODE_UNLOCKED + + * @see .LOCK_MODE_LOCKED_CLOSED + + * @see .LOCK_MODE_LOCKED_OPEN + */ + fun setDrawerLockMode(@LockMode lockMode: Int, @EdgeGravity edgeGravity: Int) { + val absGravity = GravityCompat.getAbsoluteGravity(edgeGravity, + ViewCompat.getLayoutDirection(this)) + + when (edgeGravity) { + Gravity.LEFT -> mLockModeLeft = lockMode + Gravity.RIGHT -> mLockModeRight = lockMode + GravityCompat.START -> mLockModeStart = lockMode + GravityCompat.END -> mLockModeEnd = lockMode + } + + if (lockMode != LOCK_MODE_UNLOCKED) { + // Cancel interaction in progress + val helper = if (absGravity == Gravity.LEFT) mLeftDragger else mRightDragger + helper.cancel() + } + when (lockMode) { + LOCK_MODE_LOCKED_OPEN -> { + val toOpen = findDrawerWithGravity(absGravity) + if (toOpen != null) { + openDrawer(toOpen) + } + } + LOCK_MODE_LOCKED_CLOSED -> { + val toClose = findDrawerWithGravity(absGravity) + if (toClose != null) { + closeDrawer(toClose) + } + } + }// default: do nothing + } + + /** + * Enable or disable interaction with the given drawer. + + * + * This allows the application to restrict the user's ability to open or close + * the given drawer. DrawerLayout will still respond to calls to [.openDrawer], + * [.closeDrawer] and friends if a drawer is locked. + + * + * Locking a drawer open or closed will implicitly open or close + * that drawer as appropriate. + + * @param lockMode The new lock mode for the given drawer. One of [.LOCK_MODE_UNLOCKED], + * * [.LOCK_MODE_LOCKED_CLOSED] or [.LOCK_MODE_LOCKED_OPEN]. + * * + * @param drawerView The drawer view to change the lock mode for + * * + * * + * @see .LOCK_MODE_UNLOCKED + + * @see .LOCK_MODE_LOCKED_CLOSED + + * @see .LOCK_MODE_LOCKED_OPEN + */ + fun setDrawerLockMode(@LockMode lockMode: Int, drawerView: View) { + if (!isDrawerView(drawerView)) { + throw IllegalArgumentException("View " + drawerView + " is not a " + + "drawer with appropriate layout_gravity") + } + val gravity = (drawerView.layoutParams as LayoutParams).gravity + setDrawerLockMode(lockMode, gravity) + } + + /** + * Check the lock mode of the drawer with the given gravity. + + * @param edgeGravity Gravity of the drawer to check + * * + * @return one of [.LOCK_MODE_UNLOCKED], [.LOCK_MODE_LOCKED_CLOSED] or + * * [.LOCK_MODE_LOCKED_OPEN]. + */ + @LockMode + fun getDrawerLockMode(@EdgeGravity edgeGravity: Int): Int { + val layoutDirection = ViewCompat.getLayoutDirection(this) + + when (edgeGravity) { + Gravity.LEFT -> { + if (mLockModeLeft != LOCK_MODE_UNDEFINED) { + return mLockModeLeft + } + val leftLockMode = if (layoutDirection == ViewCompat.LAYOUT_DIRECTION_LTR) + mLockModeStart + else + mLockModeEnd + if (leftLockMode != LOCK_MODE_UNDEFINED) { + return leftLockMode + } + } + Gravity.RIGHT -> { + if (mLockModeRight != LOCK_MODE_UNDEFINED) { + return mLockModeRight + } + val rightLockMode = if (layoutDirection == ViewCompat.LAYOUT_DIRECTION_LTR) + mLockModeEnd + else + mLockModeStart + if (rightLockMode != LOCK_MODE_UNDEFINED) { + return rightLockMode + } + } + GravityCompat.START -> { + if (mLockModeStart != LOCK_MODE_UNDEFINED) { + return mLockModeStart + } + val startLockMode = if (layoutDirection == ViewCompat.LAYOUT_DIRECTION_LTR) + mLockModeLeft + else + mLockModeRight + if (startLockMode != LOCK_MODE_UNDEFINED) { + return startLockMode + } + } + GravityCompat.END -> { + if (mLockModeEnd != LOCK_MODE_UNDEFINED) { + return mLockModeEnd + } + val endLockMode = if (layoutDirection == ViewCompat.LAYOUT_DIRECTION_LTR) + mLockModeRight + else + mLockModeLeft + if (endLockMode != LOCK_MODE_UNDEFINED) { + return endLockMode + } + } + } + + return LOCK_MODE_UNLOCKED + } + + /** + * Check the lock mode of the given drawer view. + + * @param drawerView Drawer view to check lock mode + * * + * @return one of [.LOCK_MODE_UNLOCKED], [.LOCK_MODE_LOCKED_CLOSED] or + * * [.LOCK_MODE_LOCKED_OPEN]. + */ + @LockMode + fun getDrawerLockMode(drawerView: View): Int { + if (!isDrawerView(drawerView)) { + throw IllegalArgumentException("View $drawerView is not a drawer") + } + val drawerGravity = (drawerView.layoutParams as LayoutParams).gravity + return getDrawerLockMode(drawerGravity) + } + + /** + * Sets the title of the drawer with the given gravity. + * + * + * When accessibility is turned on, this is the title that will be used to + * identify the drawer to the active accessibility service. + + * @param edgeGravity Gravity.LEFT, RIGHT, START or END. Expresses which + * * drawer to set the title for. + * * + * @param title The title for the drawer. + */ + fun setDrawerTitle(@EdgeGravity edgeGravity: Int, title: CharSequence) { + val absGravity = GravityCompat.getAbsoluteGravity( + edgeGravity, ViewCompat.getLayoutDirection(this)) + if (absGravity == Gravity.LEFT) { + mTitleLeft = title + } else if (absGravity == Gravity.RIGHT) { + mTitleRight = title + } + } + + /** + * Returns the title of the drawer with the given gravity. + + * @param edgeGravity Gravity.LEFT, RIGHT, START or END. Expresses which + * * drawer to return the title for. + * * + * @return The title of the drawer, or null if none set. + * * + * @see .setDrawerTitle + */ + fun getDrawerTitle(@EdgeGravity edgeGravity: Int): CharSequence? { + val absGravity = GravityCompat.getAbsoluteGravity( + edgeGravity, ViewCompat.getLayoutDirection(this)) + if (absGravity == Gravity.LEFT) { + return mTitleLeft + } else if (absGravity == Gravity.RIGHT) { + return mTitleRight + } + return null + } + + /** + * Resolve the shared state of all drawers from the component ViewDragHelpers. + * Should be called whenever a ViewDragHelper's state changes. + */ + internal fun updateDrawerState(forGravity: Int, @State activeState: Int, activeDrawer: View?) { + val leftState = mLeftDragger.viewDragState + val rightState = mRightDragger.viewDragState + + val state: Int + if (leftState == STATE_DRAGGING || rightState == STATE_DRAGGING) { + state = STATE_DRAGGING + } else if (leftState == STATE_SETTLING || rightState == STATE_SETTLING) { + state = STATE_SETTLING + } else { + state = STATE_IDLE + } + + if (activeDrawer != null && activeState == STATE_IDLE) { + val lp = activeDrawer.layoutParams as LayoutParams + if (lp.onScreen == 0f) { + dispatchOnDrawerClosed(activeDrawer) + } else if (lp.onScreen == 1f) { + dispatchOnDrawerOpened(activeDrawer) + } + } + + if (state != mDrawerState) { + mDrawerState = state + + if (mListeners != null) { + // Notify the listeners. Do that from the end of the list so that if a listener + // removes itself as the result of being called, it won't mess up with our iteration + val listenerCount = mListeners!!.size + for (i in listenerCount - 1 downTo 0) { + mListeners!![i].onDrawerStateChanged(state) + } + } + } + } + + internal fun dispatchOnDrawerClosed(drawerView: View) { + val lp = drawerView.layoutParams as LayoutParams + if (lp.openState and LayoutParams.FLAG_IS_OPENED == 1) { + lp.openState = 0 + + if (mListeners != null) { + // Notify the listeners. Do that from the end of the list so that if a listener + // removes itself as the result of being called, it won't mess up with our iteration + val listenerCount = mListeners!!.size + for (i in listenerCount - 1 downTo 0) { + mListeners!![i].onDrawerClosed(drawerView) + } + } + + updateChildrenImportantForAccessibility(drawerView, false) + + // Only send WINDOW_STATE_CHANGE if the host has window focus. This + // may change if support for multiple foreground windows (e.g. IME) + // improves. + if (hasWindowFocus()) { + val rootView = rootView + rootView?.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) + } + } + } + + internal fun dispatchOnDrawerOpened(drawerView: View) { + val lp = drawerView.layoutParams as LayoutParams + if (lp.openState and LayoutParams.FLAG_IS_OPENED == 0) { + lp.openState = LayoutParams.FLAG_IS_OPENED + if (mListeners != null) { + // Notify the listeners. Do that from the end of the list so that if a listener + // removes itself as the result of being called, it won't mess up with our iteration + val listenerCount = mListeners!!.size + for (i in listenerCount - 1 downTo 0) { + mListeners!![i].onDrawerOpened(drawerView) + } + } + + updateChildrenImportantForAccessibility(drawerView, true) + + // Only send WINDOW_STATE_CHANGE if the host has window focus. + if (hasWindowFocus()) { + sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) + } + + drawerView.requestFocus() + } + } + + private fun updateChildrenImportantForAccessibility(drawerView: View, isDrawerOpen: Boolean) { + val childCount = childCount + for (i in 0..childCount - 1) { + val child = getChildAt(i) + if (!isDrawerOpen && !isDrawerView(child) || isDrawerOpen && child === drawerView) { + // Drawer is closed and this is a content view or this is an + // open drawer view, so it should be visible. + ViewCompat.setImportantForAccessibility(child, + ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES) + } else { + ViewCompat.setImportantForAccessibility(child, + ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS) + } + } + } + + internal fun dispatchOnDrawerSlide(drawerView: View, slideOffset: Float) { + if (mListeners != null) { + // Notify the listeners. Do that from the end of the list so that if a listener + // removes itself as the result of being called, it won't mess up with our iteration + val listenerCount = mListeners!!.size + for (i in listenerCount - 1 downTo 0) { + mListeners!![i].onDrawerSlide(drawerView, slideOffset) + } + } + } + + internal fun setDrawerViewOffset(drawerView: View, slideOffset: Float) { + val lp = drawerView.layoutParams as LayoutParams + if (slideOffset == lp.onScreen) { + return + } + + lp.onScreen = slideOffset + dispatchOnDrawerSlide(drawerView, slideOffset) + } + + internal fun getDrawerViewOffset(drawerView: View?): Float { + return (drawerView?.layoutParams as LayoutParams).onScreen + } + + /** + * @return the absolute gravity of the child drawerView, resolved according + * * to the current layout direction + */ + internal fun getDrawerViewAbsoluteGravity(drawerView: View): Int { + val gravity = (drawerView.layoutParams as LayoutParams).gravity + return GravityCompat.getAbsoluteGravity(gravity, ViewCompat.getLayoutDirection(this)) + } + + internal fun checkDrawerViewAbsoluteGravity(drawerView: View, checkFor: Int): Boolean { + val absGravity = getDrawerViewAbsoluteGravity(drawerView) + return absGravity and checkFor == checkFor + } + + internal fun findOpenDrawer(): View? { + val childCount = childCount + for (i in 0..childCount - 1) { + val child = getChildAt(i) + val childLp = child.layoutParams as LayoutParams + if (childLp.openState and LayoutParams.FLAG_IS_OPENED == 1) { + return child + } + } + return null + } + + internal fun moveDrawerToOffset(drawerView: View, slideOffset: Float) { + val oldOffset = getDrawerViewOffset(drawerView) + val width = drawerView.width + val oldPos = (width * oldOffset).toInt() + val newPos = (width * slideOffset).toInt() + val dx = newPos - oldPos + + drawerView.offsetLeftAndRight( + if (checkDrawerViewAbsoluteGravity(drawerView, Gravity.LEFT)) dx else -dx) + setDrawerViewOffset(drawerView, slideOffset) + } + + /** + * @param gravity the gravity of the child to return. If specified as a + * * relative value, it will be resolved according to the current + * * layout direction. + * * + * @return the drawer with the specified gravity + */ + internal fun findDrawerWithGravity(gravity: Int): View? { + val absHorizGravity = GravityCompat.getAbsoluteGravity( + gravity, ViewCompat.getLayoutDirection(this)) and Gravity.HORIZONTAL_GRAVITY_MASK + val childCount = childCount + for (i in 0..childCount - 1) { + val child = getChildAt(i) + val childAbsGravity = getDrawerViewAbsoluteGravity(child) + if (childAbsGravity and Gravity.HORIZONTAL_GRAVITY_MASK == absHorizGravity) { + return child + } + } + return null + } + + override fun onDetachedFromWindow() { + super.onDetachedFromWindow() + mFirstLayout = true + } + + override fun onAttachedToWindow() { + super.onAttachedToWindow() + mFirstLayout = true + } + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + var widthMode = View.MeasureSpec.getMode(widthMeasureSpec) + var heightMode = View.MeasureSpec.getMode(heightMeasureSpec) + var widthSize = View.MeasureSpec.getSize(widthMeasureSpec) + var heightSize = View.MeasureSpec.getSize(heightMeasureSpec) + + if (widthMode != View.MeasureSpec.EXACTLY || heightMode != View.MeasureSpec.EXACTLY) { + if (isInEditMode) { + // Don't crash the layout editor. Consume all of the space if specified + // or pick a magic number from thin air otherwise. + // TODO Better communication with tools of this bogus state. + // It will crash on a real device. + if (widthMode == View.MeasureSpec.AT_MOST) { + widthMode = View.MeasureSpec.EXACTLY + } else if (widthMode == View.MeasureSpec.UNSPECIFIED) { + widthMode = View.MeasureSpec.EXACTLY + widthSize = 300 + } + if (heightMode == View.MeasureSpec.AT_MOST) { + heightMode = View.MeasureSpec.EXACTLY + } else if (heightMode == View.MeasureSpec.UNSPECIFIED) { + heightMode = View.MeasureSpec.EXACTLY + heightSize = 300 + } + } else { + throw IllegalArgumentException( + "DrawerLayout must be measured with MeasureSpec.EXACTLY.") + } + } + + setMeasuredDimension(widthSize, heightSize) + + val applyInsets = mLastInsets != null && ViewCompat.getFitsSystemWindows(this) + val layoutDirection = ViewCompat.getLayoutDirection(this) + + // Only one drawer is permitted along each vertical edge (left / right). These two booleans + // are tracking the presence of the edge drawers. + var hasDrawerOnLeftEdge = false + var hasDrawerOnRightEdge = false + val childCount = childCount + for (i in 0..childCount - 1) { + val child = getChildAt(i) + + if (child.visibility == View.GONE) { + continue + } + + val lp = child.layoutParams as LayoutParams + + if (applyInsets) { + val cgrav = GravityCompat.getAbsoluteGravity(lp.gravity, layoutDirection) + if (ViewCompat.getFitsSystemWindows(child)) { + IMPL.dispatchChildInsets(child, mLastInsets, cgrav) + } else { + IMPL.applyMarginInsets(lp, mLastInsets, cgrav) + } + } + + if (isContentView(child)) { + // Content views get measured at exactly the layout's size. + val contentWidthSpec = View.MeasureSpec.makeMeasureSpec( + widthSize - lp.leftMargin - lp.rightMargin, View.MeasureSpec.EXACTLY) + val contentHeightSpec = View.MeasureSpec.makeMeasureSpec( + heightSize - lp.topMargin - lp.bottomMargin, View.MeasureSpec.EXACTLY) + child.measure(contentWidthSpec, contentHeightSpec) + } else if (isDrawerView(child)) { + if (SET_DRAWER_SHADOW_FROM_ELEVATION) { + if (ViewCompat.getElevation(child) != mDrawerElevation) { + ViewCompat.setElevation(child, mDrawerElevation) + } + } + + + @EdgeGravity val childGravity = getDrawerViewAbsoluteGravity(child) and Gravity.HORIZONTAL_GRAVITY_MASK + // Note that the isDrawerView check guarantees that childGravity here is either + // LEFT or RIGHT + val isLeftEdgeDrawer = childGravity == Gravity.LEFT + if (isLeftEdgeDrawer && hasDrawerOnLeftEdge || !isLeftEdgeDrawer && hasDrawerOnRightEdge) { + throw IllegalStateException("Child drawer has absolute gravity " + + gravityToString(childGravity) + " but this " + TAG + " already has a " + + "drawer view along that edge") + } + if (isLeftEdgeDrawer) { + hasDrawerOnLeftEdge = true + } else { + hasDrawerOnRightEdge = true + } + val drawerWidthSpec = ViewGroup.getChildMeasureSpec(widthMeasureSpec, + mMinDrawerMargin + lp.leftMargin + lp.rightMargin, + lp.width) + val drawerHeightSpec = ViewGroup.getChildMeasureSpec(heightMeasureSpec, + lp.topMargin + lp.bottomMargin, + lp.height) + child.measure(drawerWidthSpec, drawerHeightSpec) + } else { + throw IllegalStateException("Child " + child + " at index " + i + + " does not have a valid layout_gravity - must be Gravity.LEFT, " + + "Gravity.RIGHT or Gravity.NO_GRAVITY") + } + } + } + + private fun resolveShadowDrawables() { + if (SET_DRAWER_SHADOW_FROM_ELEVATION) { + return + } + mShadowLeftResolved = resolveLeftShadow() + mShadowRightResolved = resolveRightShadow() + } + + private fun resolveLeftShadow(): Drawable? { + val layoutDirection = ViewCompat.getLayoutDirection(this) + // Prefer shadows defined with start/end gravity over left and right. + if (layoutDirection == ViewCompat.LAYOUT_DIRECTION_LTR) { + if (mShadowStart != null) { + // Correct drawable layout direction, if needed. + mirror(mShadowStart, layoutDirection) + return mShadowStart + } + } else { + if (mShadowEnd != null) { + // Correct drawable layout direction, if needed. + mirror(mShadowEnd, layoutDirection) + return mShadowEnd + } + } + return mShadowLeft + } + + private fun resolveRightShadow(): Drawable? { + val layoutDirection = ViewCompat.getLayoutDirection(this) + if (layoutDirection == ViewCompat.LAYOUT_DIRECTION_LTR) { + if (mShadowEnd != null) { + // Correct drawable layout direction, if needed. + mirror(mShadowEnd, layoutDirection) + return mShadowEnd + } + } else { + if (mShadowStart != null) { + // Correct drawable layout direction, if needed. + mirror(mShadowStart, layoutDirection) + return mShadowStart + } + } + return mShadowRight + } + + /** + * Change the layout direction of the given drawable. + * Return true if auto-mirror is supported and drawable's layout direction can be changed. + * Otherwise, return false. + */ + private fun mirror(drawable: Drawable?, layoutDirection: Int): Boolean { + if (drawable == null || !DrawableCompat.isAutoMirrored(drawable)) { + return false + } + + DrawableCompat.setLayoutDirection(drawable, layoutDirection) + return true + } + + override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) { + mInLayout = true + val width = r - l + val childCount = childCount + for (i in 0..childCount - 1) { + val child = getChildAt(i) + + if (child.visibility == View.GONE) { + continue + } + + val lp = child.layoutParams as LayoutParams + + if (isContentView(child)) { + child.layout(lp.leftMargin, lp.topMargin, + lp.leftMargin + child.measuredWidth, + lp.topMargin + child.measuredHeight) + } else { // Drawer, if it wasn't onMeasure would have thrown an exception. + val childWidth = child.measuredWidth + val childHeight = child.measuredHeight + val childLeft: Int + + val newOffset: Float + if (checkDrawerViewAbsoluteGravity(child, Gravity.LEFT)) { + childLeft = -childWidth + (childWidth * lp.onScreen).toInt() + newOffset = (childWidth + childLeft).toFloat() / childWidth + } else { // Right; onMeasure checked for us. + childLeft = width - (childWidth * lp.onScreen).toInt() + newOffset = (width - childLeft).toFloat() / childWidth + } + + val changeOffset = newOffset != lp.onScreen + + val vgrav = lp.gravity and Gravity.VERTICAL_GRAVITY_MASK + + when (vgrav) { + Gravity.TOP -> { + child.layout(childLeft, lp.topMargin, childLeft + childWidth, + lp.topMargin + childHeight) + } + + Gravity.BOTTOM -> { + val height = b - t + child.layout(childLeft, + height - lp.bottomMargin - child.measuredHeight, + childLeft + childWidth, + height - lp.bottomMargin) + } + + Gravity.CENTER_VERTICAL -> { + val height = b - t + var childTop = (height - childHeight) / 2 + + // Offset for margins. If things don't fit right because of + // bad measurement before, oh well. + if (childTop < lp.topMargin) { + childTop = lp.topMargin + } else if (childTop + childHeight > height - lp.bottomMargin) { + childTop = height - lp.bottomMargin - childHeight + } + child.layout(childLeft, childTop, childLeft + childWidth, + childTop + childHeight) + } + else -> { + child.layout(childLeft, lp.topMargin, childLeft + childWidth, + lp.topMargin + childHeight) + } + } + + if (changeOffset) { + setDrawerViewOffset(child, newOffset) + } + + val newVisibility = if (lp.onScreen > 0) View.VISIBLE else View.INVISIBLE + if (child.visibility != newVisibility) { + child.visibility = newVisibility + } + } + } + mInLayout = false + mFirstLayout = false + } + + + @SuppressLint("MissingSuperCall") + override fun requestLayout() { + if (!mInLayout) { + super.requestLayout() + } + } + + override fun computeScroll() { + val childCount = childCount + var scrimOpacity = 0f + for (i in 0..childCount - 1) { + val onscreen = (getChildAt(i).layoutParams as LayoutParams).onScreen + scrimOpacity = Math.max(scrimOpacity, onscreen) + } + mScrimOpacity = scrimOpacity + + // "|" used on purpose; both need to run. + if (mLeftDragger.continueSettling(true) or mRightDragger.continueSettling(true)) { + ViewCompat.postInvalidateOnAnimation(this) + } + } + + /** + * Set a drawable to draw in the insets area for the status bar. + * Note that this will only be activated if this DrawerLayout fitsSystemWindows. + + * @param bg Background drawable to draw behind the status bar + */ + fun setStatusBarBackground(bg: Drawable) { + statusBarBackgroundDrawable = bg + invalidate() + } + + /** + * Set a drawable to draw in the insets area for the status bar. + * Note that this will only be activated if this DrawerLayout fitsSystemWindows. + + * @param resId Resource id of a background drawable to draw behind the status bar + */ + fun setStatusBarBackground(resId: Int) { + statusBarBackgroundDrawable = if (resId != 0) ContextCompat.getDrawable(context, resId) else null + invalidate() + } + + /** + * Set a drawable to draw in the insets area for the status bar. + * Note that this will only be activated if this DrawerLayout fitsSystemWindows. + + * @param color Color to use as a background drawable to draw behind the status bar + * * in 0xAARRGGBB format. + */ + fun setStatusBarBackgroundColor(@ColorInt color: Int) { + statusBarBackgroundDrawable = ColorDrawable(color) + invalidate() + } + + override fun onRtlPropertiesChanged(layoutDirection: Int) { + resolveShadowDrawables() + } + + public override fun onDraw(c: Canvas) { + super.onDraw(c) + if (mDrawStatusBarBackground && statusBarBackgroundDrawable != null) { + val inset = IMPL.getTopInset(mLastInsets) + if (inset > 0) { + statusBarBackgroundDrawable!!.setBounds(0, 0, width, inset) + statusBarBackgroundDrawable!!.draw(c) + } + } + } + + override fun drawChild(canvas: Canvas, child: View, drawingTime: Long): Boolean { + val height = height + val drawingContent = isContentView(child) + var clipLeft = 0 + var clipRight = width + + val restoreCount = canvas.save() + if (drawingContent) { + val childCount = childCount + for (i in 0..childCount - 1) { + val v = getChildAt(i) + if (v === child || v.visibility != View.VISIBLE || + !hasOpaqueBackground(v) || !isDrawerView(v) || + v.height < height) { + continue + } + + if (checkDrawerViewAbsoluteGravity(v, Gravity.LEFT)) { + val vright = v.right + if (vright > clipLeft) clipLeft = vright + } else { + val vleft = v.left + if (vleft < clipRight) clipRight = vleft + } + } + canvas.clipRect(clipLeft, 0, clipRight, getHeight()) + } + val result = super.drawChild(canvas, child, drawingTime) + canvas.restoreToCount(restoreCount) + + if (mScrimOpacity > 0 && drawingContent) { + val baseAlpha = (mScrimColor and 0xff000000.toInt()).ushr(24) + val imag = (baseAlpha * mScrimOpacity).toInt() + val color = imag shl 24 or (mScrimColor and 0xffffff) + mScrimPaint.color = color + + canvas.drawRect(clipLeft.toFloat(), 0f, clipRight.toFloat(), getHeight().toFloat(), mScrimPaint) + } else if (mShadowLeftResolved != null && checkDrawerViewAbsoluteGravity(child, Gravity.LEFT)) { + val shadowWidth = mShadowLeftResolved!!.intrinsicWidth + val childRight = child.right + val drawerPeekDistance = mLeftDragger.edgeSize + val alpha = Math.max(0f, Math.min(childRight.toFloat() / drawerPeekDistance, 1f)) + mShadowLeftResolved!!.setBounds(childRight, child.top, + childRight + shadowWidth, child.bottom) + mShadowLeftResolved!!.alpha = (0xff * alpha).toInt() + mShadowLeftResolved!!.draw(canvas) + } else if (mShadowRightResolved != null && checkDrawerViewAbsoluteGravity(child, Gravity.RIGHT)) { + val shadowWidth = mShadowRightResolved!!.intrinsicWidth + val childLeft = child.left + val showing = width - childLeft + val drawerPeekDistance = mRightDragger.edgeSize + val alpha = Math.max(0f, Math.min(showing.toFloat() / drawerPeekDistance, 1f)) + mShadowRightResolved!!.setBounds(childLeft - shadowWidth, child.top, + childLeft, child.bottom) + mShadowRightResolved!!.alpha = (0xff * alpha).toInt() + mShadowRightResolved!!.draw(canvas) + } + return result + } + + internal fun isContentView(child: View): Boolean { + return (child.layoutParams as LayoutParams).gravity == Gravity.NO_GRAVITY + } + + internal fun isDrawerView(child: View?): Boolean { + val gravity = (child?.layoutParams as LayoutParams).gravity + val absGravity = GravityCompat.getAbsoluteGravity(gravity, + ViewCompat.getLayoutDirection(child)) + if (absGravity and Gravity.LEFT != 0) { + // This child is a left-edge drawer + return true + } + if (absGravity and Gravity.RIGHT != 0) { + // This child is a right-edge drawer + return true + } + return false + } + + override fun onInterceptTouchEvent(ev: MotionEvent): Boolean { + val action = MotionEventCompat.getActionMasked(ev) + + // "|" used deliberately here; both methods should be invoked. + val interceptForDrag = mLeftDragger.shouldInterceptTouchEvent(ev) or mRightDragger.shouldInterceptTouchEvent(ev) + + var interceptForTap = false + + when (action) { + MotionEvent.ACTION_DOWN -> { + val x = ev.x + val y = ev.y + mInitialMotionX = x + mInitialMotionY = y + if (mScrimOpacity > 0) { + val child = mLeftDragger.findTopChildUnder(x.toInt(), y.toInt()) + if (child != null && isContentView(child)) { + interceptForTap = true + } + } + mDisallowInterceptRequested = false + mChildrenCanceledTouch = false + } + + MotionEvent.ACTION_MOVE -> { + // If we cross the touch slop, don't perform the delayed peek for an edge touch. + if (mLeftDragger.checkTouchSlop(ViewDragHelper.DIRECTION_ALL)) { + mLeftCallback.removeCallbacks() + mRightCallback.removeCallbacks() + } + } + + MotionEvent.ACTION_CANCEL, MotionEvent.ACTION_UP -> { + closeDrawers(true) + mDisallowInterceptRequested = false + mChildrenCanceledTouch = false + } + } + + return interceptForDrag || interceptForTap || hasPeekingDrawer() || mChildrenCanceledTouch + } + + override fun onTouchEvent(ev: MotionEvent): Boolean { + mLeftDragger.processTouchEvent(ev) + mRightDragger.processTouchEvent(ev) + + val action = ev.action + val wantTouchEvents = true + + when (action and MotionEventCompat.ACTION_MASK) { + MotionEvent.ACTION_DOWN -> { + val x = ev.x + val y = ev.y + mInitialMotionX = x + mInitialMotionY = y + mDisallowInterceptRequested = false + mChildrenCanceledTouch = false + } + + MotionEvent.ACTION_UP -> { + val x = ev.x + val y = ev.y + var peekingOnly = true + val touchedView = mLeftDragger.findTopChildUnder(x.toInt(), y.toInt()) + if (touchedView != null && isContentView(touchedView)) { + val dx = x - mInitialMotionX + val dy = y - mInitialMotionY + val slop = mLeftDragger.touchSlop + if (dx * dx + dy * dy < slop * slop) { + // Taps close a dimmed open drawer but only if it isn't locked open. + val openDrawer = findOpenDrawer() + if (openDrawer != null) { + peekingOnly = getDrawerLockMode(openDrawer) == LOCK_MODE_LOCKED_OPEN + } + } + } + closeDrawers(peekingOnly) + mDisallowInterceptRequested = false + } + + MotionEvent.ACTION_CANCEL -> { + closeDrawers(true) + mDisallowInterceptRequested = false + mChildrenCanceledTouch = false + } + } + + return wantTouchEvents + } + + override fun requestDisallowInterceptTouchEvent(disallowIntercept: Boolean) { + if (CHILDREN_DISALLOW_INTERCEPT || !mLeftDragger.isEdgeTouched(ViewDragHelper.EDGE_LEFT) && !mRightDragger.isEdgeTouched(ViewDragHelper.EDGE_RIGHT)) { + // If we have an edge touch we want to skip this and track it for later instead. + super.requestDisallowInterceptTouchEvent(disallowIntercept) + } + mDisallowInterceptRequested = disallowIntercept + if (disallowIntercept) { + closeDrawers(true) + } + } + + /** + * Close all currently open drawer views by animating them out of view. + */ + fun closeDrawers() { + closeDrawers(false) + } + + internal fun closeDrawers(peekingOnly: Boolean) { + var needsInvalidate = false + val childCount = childCount + for (i in 0..childCount - 1) { + val child = getChildAt(i) + val lp = child.layoutParams as LayoutParams + + if (!isDrawerView(child) || peekingOnly && !lp.isPeeking) { + continue + } + + val childWidth = child.width + + if (checkDrawerViewAbsoluteGravity(child, Gravity.LEFT)) { + needsInvalidate = needsInvalidate or mLeftDragger.smoothSlideViewTo(child, + -childWidth, child.top) + } else { + needsInvalidate = needsInvalidate or mRightDragger.smoothSlideViewTo(child, + width, child.top) + } + + lp.isPeeking = false + } + + mLeftCallback.removeCallbacks() + mRightCallback.removeCallbacks() + + if (needsInvalidate) { + invalidate() + } + } + + /** + * Open the specified drawer view. + + * @param drawerView Drawer view to open + * * + * @param animate Whether opening of the drawer should be animated. + */ + @JvmOverloads fun openDrawer(drawerView: View, animate: Boolean = true) { + if (!isDrawerView(drawerView)) { + throw IllegalArgumentException("View $drawerView is not a sliding drawer") + } + + val lp = drawerView.layoutParams as LayoutParams + if (mFirstLayout) { + lp.onScreen = 1f + lp.openState = LayoutParams.FLAG_IS_OPENED + + updateChildrenImportantForAccessibility(drawerView, true) + } else if (animate) { + lp.openState = lp.openState or LayoutParams.FLAG_IS_OPENING + + if (checkDrawerViewAbsoluteGravity(drawerView, Gravity.LEFT)) { + mLeftDragger.smoothSlideViewTo(drawerView, 0, drawerView.top) + } else { + mRightDragger.smoothSlideViewTo(drawerView, width - drawerView.width, + drawerView.top) + } + } else { + moveDrawerToOffset(drawerView, 1f) + updateDrawerState(lp.gravity, STATE_IDLE, drawerView) + drawerView.visibility = View.VISIBLE + } + invalidate() + } + + /** + * Open the specified drawer. + + * @param gravity Gravity.LEFT to move the left drawer or Gravity.RIGHT for the right. + * * GravityCompat.START or GravityCompat.END may also be used. + * * + * @param animate Whether opening of the drawer should be animated. + */ + @JvmOverloads fun openDrawer(@EdgeGravity gravity: Int, animate: Boolean = true) { + val drawerView = findDrawerWithGravity(gravity) ?: throw IllegalArgumentException("No drawer view found with gravity " + gravityToString(gravity)) + openDrawer(drawerView, animate) + } + + /** + * Close the specified drawer view. + + * @param drawerView Drawer view to close + * * + * @param animate Whether closing of the drawer should be animated. + */ + @JvmOverloads fun closeDrawer(drawerView: View, animate: Boolean = true) { + if (!isDrawerView(drawerView)) { + throw IllegalArgumentException("View $drawerView is not a sliding drawer") + } + + val lp = drawerView.layoutParams as LayoutParams + if (mFirstLayout) { + lp.onScreen = 0f + lp.openState = 0 + } else if (animate) { + lp.openState = lp.openState or LayoutParams.FLAG_IS_CLOSING + + if (checkDrawerViewAbsoluteGravity(drawerView, Gravity.LEFT)) { + mLeftDragger.smoothSlideViewTo(drawerView, -drawerView.width, + drawerView.top) + } else { + mRightDragger.smoothSlideViewTo(drawerView, width, drawerView.top) + } + } else { + moveDrawerToOffset(drawerView, 0f) + updateDrawerState(lp.gravity, STATE_IDLE, drawerView) + drawerView.visibility = View.INVISIBLE + } + invalidate() + } + + /** + * Close the specified drawer. + + * @param gravity Gravity.LEFT to move the left drawer or Gravity.RIGHT for the right. + * * GravityCompat.START or GravityCompat.END may also be used. + * * + * @param animate Whether closing of the drawer should be animated. + */ + @JvmOverloads fun closeDrawer(@EdgeGravity gravity: Int, animate: Boolean = true) { + val drawerView = findDrawerWithGravity(gravity) ?: throw IllegalArgumentException("No drawer view found with gravity " + gravityToString(gravity)) + closeDrawer(drawerView, animate) + } + + /** + * Check if the given drawer view is currently in an open state. + * To be considered "open" the drawer must have settled into its fully + * visible state. To check for partial visibility use + * [.isDrawerVisible]. + + * @param drawer Drawer view to check + * * + * @return true if the given drawer view is in an open state + * * + * @see .isDrawerVisible + */ + fun isDrawerOpen(drawer: View): Boolean { + if (!isDrawerView(drawer)) { + throw IllegalArgumentException("View $drawer is not a drawer") + } + val drawerLp = drawer.layoutParams as LayoutParams + return drawerLp.openState and LayoutParams.FLAG_IS_OPENED == 1 + } + + /** + * Check if the given drawer view is currently in an open state. + * To be considered "open" the drawer must have settled into its fully + * visible state. If there is no drawer with the given gravity this method + * will return false. + + * @param drawerGravity Gravity of the drawer to check + * * + * @return true if the given drawer view is in an open state + */ + fun isDrawerOpen(@EdgeGravity drawerGravity: Int): Boolean { + val drawerView = findDrawerWithGravity(drawerGravity) + if (drawerView != null) { + return isDrawerOpen(drawerView) + } + return false + } + + /** + * Check if a given drawer view is currently visible on-screen. The drawer + * may be only peeking onto the screen, fully extended, or anywhere inbetween. + + * @param drawer Drawer view to check + * * + * @return true if the given drawer is visible on-screen + * * + * @see .isDrawerOpen + */ + fun isDrawerVisible(drawer: View): Boolean { + if (!isDrawerView(drawer)) { + throw IllegalArgumentException("View $drawer is not a drawer") + } + return (drawer.layoutParams as LayoutParams).onScreen > 0 + } + + /** + * Check if a given drawer view is currently visible on-screen. The drawer + * may be only peeking onto the screen, fully extended, or anywhere in between. + * If there is no drawer with the given gravity this method will return false. + + * @param drawerGravity Gravity of the drawer to check + * * + * @return true if the given drawer is visible on-screen + */ + fun isDrawerVisible(@EdgeGravity drawerGravity: Int): Boolean { + val drawerView = findDrawerWithGravity(drawerGravity) + if (drawerView != null) { + return isDrawerVisible(drawerView) + } + return false + } + + private fun hasPeekingDrawer(): Boolean { + val childCount = childCount + for (i in 0..childCount - 1) { + val lp = getChildAt(i).layoutParams as LayoutParams + if (lp.isPeeking) { + return true + } + } + return false + } + + override fun generateDefaultLayoutParams(): ViewGroup.LayoutParams { + return LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT) + } + + override fun generateLayoutParams(p: ViewGroup.LayoutParams): ViewGroup.LayoutParams { + return if (p is LayoutParams) + LayoutParams(p) + else if (p is ViewGroup.MarginLayoutParams) + LayoutParams(p) + else + LayoutParams(p) + } + + override fun checkLayoutParams(p: ViewGroup.LayoutParams): Boolean { + return p is LayoutParams && super.checkLayoutParams(p) + } + + override fun generateLayoutParams(attrs: AttributeSet): ViewGroup.LayoutParams { + return LayoutParams(context, attrs) + } + + override fun addFocusables(views: ArrayList, direction: Int, focusableMode: Int) { + if (descendantFocusability == ViewGroup.FOCUS_BLOCK_DESCENDANTS) { + return + } + + // Only the views in the open drawers are focusables. Add normal child views when + // no drawers are opened. + val childCount = childCount + var isDrawerOpen = false + for (i in 0..childCount - 1) { + val child = getChildAt(i) + if (isDrawerView(child)) { + if (isDrawerOpen(child)) { + isDrawerOpen = true + child.addFocusables(views, direction, focusableMode) + } + } else { + mNonDrawerViews.add(child) + } + } + + if (!isDrawerOpen) { + val nonDrawerViewsCount = mNonDrawerViews.size + for (i in 0..nonDrawerViewsCount - 1) { + val child = mNonDrawerViews[i] + if (child.visibility == View.VISIBLE) { + child.addFocusables(views, direction, focusableMode) + } + } + } + + mNonDrawerViews.clear() + } + + private fun hasVisibleDrawer(): Boolean { + return findVisibleDrawer() != null + } + + private fun findVisibleDrawer(): View? { + val childCount = childCount + for (i in 0..childCount - 1) { + val child = getChildAt(i) + if (isDrawerView(child) && isDrawerVisible(child)) { + return child + } + } + return null + } + + internal fun cancelChildViewTouch() { + // Cancel child touches + if (!mChildrenCanceledTouch) { + val now = SystemClock.uptimeMillis() + val cancelEvent = MotionEvent.obtain(now, now, + MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0) + val childCount = childCount + for (i in 0..childCount - 1) { + getChildAt(i).dispatchTouchEvent(cancelEvent) + } + cancelEvent.recycle() + mChildrenCanceledTouch = true + } + } + + override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean { + if (keyCode == KeyEvent.KEYCODE_BACK && hasVisibleDrawer()) { + KeyEventCompat.startTracking(event) + return true + } + return super.onKeyDown(keyCode, event) + } + + override fun onKeyUp(keyCode: Int, event: KeyEvent): Boolean { + if (keyCode == KeyEvent.KEYCODE_BACK) { + val visibleDrawer = findVisibleDrawer() + if (visibleDrawer != null && getDrawerLockMode(visibleDrawer) == LOCK_MODE_UNLOCKED) { + closeDrawers() + } + return visibleDrawer != null + } + return super.onKeyUp(keyCode, event) + } + + override fun onRestoreInstanceState(state: Parcelable) { + if (state !is SavedState) { + super.onRestoreInstanceState(state) + return + } + + val ss = state + super.onRestoreInstanceState(ss.superState) + + if (ss.openDrawerGravity != Gravity.NO_GRAVITY) { + val toOpen = findDrawerWithGravity(ss.openDrawerGravity) + if (toOpen != null) { + openDrawer(toOpen) + } + } + + if (ss.lockModeLeft != LOCK_MODE_UNDEFINED) { + setDrawerLockMode(ss.lockModeLeft, Gravity.LEFT) + } + if (ss.lockModeRight != LOCK_MODE_UNDEFINED) { + setDrawerLockMode(ss.lockModeRight, Gravity.RIGHT) + } + if (ss.lockModeStart != LOCK_MODE_UNDEFINED) { + setDrawerLockMode(ss.lockModeStart, GravityCompat.START) + } + if (ss.lockModeEnd != LOCK_MODE_UNDEFINED) { + setDrawerLockMode(ss.lockModeEnd, GravityCompat.END) + } + } + + override fun onSaveInstanceState(): Parcelable? { + val superState = super.onSaveInstanceState() + val ss = SavedState(superState) + + val childCount = childCount + for (i in 0..childCount - 1) { + val child = getChildAt(i) + val lp = child.layoutParams as LayoutParams + // Is the current child fully opened (that is, not closing)? + val isOpenedAndNotClosing = lp.openState == LayoutParams.FLAG_IS_OPENED + // Is the current child opening? + val isClosedAndOpening = lp.openState == LayoutParams.FLAG_IS_OPENING + if (isOpenedAndNotClosing || isClosedAndOpening) { + // If one of the conditions above holds, save the child's gravity + // so that we open that child during state restore. + ss.openDrawerGravity = lp.gravity + break + } + } + + ss.lockModeLeft = mLockModeLeft + ss.lockModeRight = mLockModeRight + ss.lockModeStart = mLockModeStart + ss.lockModeEnd = mLockModeEnd + + return ss + } + + override fun addView(child: View, index: Int, params: ViewGroup.LayoutParams) { + super.addView(child, index, params) + + val openDrawer = findOpenDrawer() + if (openDrawer != null || isDrawerView(child)) { + // A drawer is already open or the new view is a drawer, so the + // new view should start out hidden. + ViewCompat.setImportantForAccessibility(child, + ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS) + } else { + // Otherwise this is a content view and no drawer is open, so the + // new view should start out visible. + ViewCompat.setImportantForAccessibility(child, + ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES) + } + + // We only need a delegate here if the framework doesn't understand + // NO_HIDE_DESCENDANTS importance. + if (!CAN_HIDE_DESCENDANTS) { + ViewCompat.setAccessibilityDelegate(child, mChildAccessibilityDelegate) + } + } + + /** + * State persisted across instances + */ + private class SavedState : AbsSavedState { + internal var openDrawerGravity = Gravity.NO_GRAVITY + internal var lockModeLeft: Int=0 + internal var lockModeRight: Int = 0 + internal var lockModeStart: Int = 0 + internal var lockModeEnd: Int = 0 + + constructor(`in`: Parcel, loader: ClassLoader) : super(`in`, loader) { + openDrawerGravity = `in`.readInt() + lockModeLeft = `in`.readInt() + lockModeRight = `in`.readInt() + lockModeStart = `in`.readInt() + lockModeEnd = `in`.readInt() + } + + constructor(superState: Parcelable) : super(superState) {} + + override fun writeToParcel(dest: Parcel, flags: Int) { + super.writeToParcel(dest, flags) + dest.writeInt(openDrawerGravity) + dest.writeInt(lockModeLeft) + dest.writeInt(lockModeRight) + dest.writeInt(lockModeStart) + dest.writeInt(lockModeEnd) + } + + companion object { + + val CREATOR: Parcelable.Creator = ParcelableCompat.newCreator( + object : ParcelableCompatCreatorCallbacks { + override fun createFromParcel(`in`: Parcel, loader: ClassLoader): SavedState { + return SavedState(`in`, loader) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + }) + } + } + + private inner class ViewDragCallback(private val mAbsGravity: Int) : ViewDragHelper.Callback() { + private var mDragger: ViewDragHelper? = null + + private val mPeekRunnable = Runnable { peekDrawer() } + + fun setDragger(dragger: ViewDragHelper) { + mDragger = dragger + } + + fun removeCallbacks() { + this@DrawerLayout.removeCallbacks(mPeekRunnable) + } + + override fun tryCaptureView(child: View, pointerId: Int): Boolean { + // Only capture views where the gravity matches what we're looking for. + // This lets us use two ViewDragHelpers, one for each side drawer. + return isDrawerView(child) && checkDrawerViewAbsoluteGravity(child, mAbsGravity) + && getDrawerLockMode(child) == LOCK_MODE_UNLOCKED + } + + override fun onViewDragStateChanged(state: Int) { + updateDrawerState(mAbsGravity, state, mDragger!!.capturedView) + } + + override fun onViewPositionChanged(changedView: View?, left: Int, top: Int, dx: Int, dy: Int) { + val offset: Float + val childWidth = changedView!!.width + + // This reverses the positioning shown in onLayout. + if (checkDrawerViewAbsoluteGravity(changedView, Gravity.LEFT)) { + offset = (childWidth + left).toFloat() / childWidth + } else { + val width = width + offset = (width - left).toFloat() / childWidth + } + setDrawerViewOffset(changedView, offset) + changedView.visibility = if (offset == 0f) View.INVISIBLE else View.VISIBLE + invalidate() + } + + override fun onViewCaptured(capturedChild: View?, activePointerId: Int) { + val lp = capturedChild!!.layoutParams as LayoutParams + lp.isPeeking = false + + closeOtherDrawer() + } + + private fun closeOtherDrawer() { + val otherGrav = if (mAbsGravity == Gravity.LEFT) Gravity.RIGHT else Gravity.LEFT + val toClose = findDrawerWithGravity(otherGrav) + if (toClose != null) { + closeDrawer(toClose) + } + } + + override fun onViewReleased(releasedChild: View?, xvel: Float, yvel: Float) { + // Offset is how open the drawer is, therefore left/right values + // are reversed from one another. + val offset = getDrawerViewOffset(releasedChild) + val childWidth = releasedChild!!.width + + val left: Int + if (checkDrawerViewAbsoluteGravity(releasedChild, Gravity.LEFT)) { + left = if (xvel > 0 || xvel == 0f && offset > 0.5f) 0 else -childWidth + } else { + val width = width + left = if (xvel < 0 || xvel == 0f && offset > 0.5f) width - childWidth else width + } + + mDragger!!.settleCapturedViewAt(left, releasedChild.top) + invalidate() + } + + override fun onEdgeTouched(edgeFlags: Int, pointerId: Int) { + postDelayed(mPeekRunnable, PEEK_DELAY.toLong()) + } + + private fun peekDrawer() { + val toCapture: View? + val childLeft: Int + val peekDistance = mDragger!!.edgeSize + val leftEdge = mAbsGravity == Gravity.LEFT + if (leftEdge) { + toCapture = findDrawerWithGravity(Gravity.LEFT) + childLeft = (if (toCapture != null) -toCapture.width else 0) + peekDistance + } else { + toCapture = findDrawerWithGravity(Gravity.RIGHT) + childLeft = width - peekDistance + } + // Only peek if it would mean making the drawer more visible and the drawer isn't locked + if (toCapture != null && (leftEdge && toCapture.left < childLeft || !leftEdge && toCapture.left > childLeft) && + getDrawerLockMode(toCapture) == LOCK_MODE_UNLOCKED) { + val lp = toCapture.layoutParams as LayoutParams + mDragger!!.smoothSlideViewTo(toCapture, childLeft, toCapture.top) + lp.isPeeking = true + invalidate() + + closeOtherDrawer() + + cancelChildViewTouch() + } + } + + override fun onEdgeLock(edgeFlags: Int): Boolean { + if (ALLOW_EDGE_LOCK) { + val drawer = findDrawerWithGravity(mAbsGravity) + if (drawer != null && !isDrawerOpen(drawer)) { + closeDrawer(drawer) + } + return true + } + return false + } + + override fun onEdgeDragStarted(edgeFlags: Int, pointerId: Int) { + val toCapture: View? + if (edgeFlags and ViewDragHelper.EDGE_LEFT == ViewDragHelper.EDGE_LEFT) { + toCapture = findDrawerWithGravity(Gravity.LEFT) + } else { + toCapture = findDrawerWithGravity(Gravity.RIGHT) + } + + if (toCapture != null && getDrawerLockMode(toCapture) == LOCK_MODE_UNLOCKED) { + mDragger!!.captureChildView(toCapture, pointerId) + } + } + + override fun getViewHorizontalDragRange(child: View?): Int { + return if (isDrawerView(child)) child!!.width else 0 + } + + override fun clampViewPositionHorizontal(child: View?, left: Int, dx: Int): Int { + if (checkDrawerViewAbsoluteGravity(child!!, Gravity.LEFT)) { + return Math.max(-child!!.width, Math.min(left, 0)) + } else { + val width = width + return Math.max(width - child!!.width, Math.min(left, width)) + } + } + + override fun clampViewPositionVertical(child: View?, top: Int, dy: Int): Int { + return child!!.top + } + } + + class LayoutParams : ViewGroup.MarginLayoutParams { + + var gravity = Gravity.NO_GRAVITY + var onScreen: Float = 0.toFloat() + var isPeeking: Boolean = false + var openState: Int = 0 + + constructor(c: Context, attrs: AttributeSet) : super(c, attrs) { + + val a = c.obtainStyledAttributes(attrs, LAYOUT_ATTRS) + this.gravity = a.getInt(0, Gravity.NO_GRAVITY) + a.recycle() + } + + constructor(width: Int, height: Int) : super(width, height) {} + + constructor(width: Int, height: Int, gravity: Int) : this(width, height) { + this.gravity = gravity + } + + constructor(source: LayoutParams) : super(source) { + this.gravity = source.gravity + } + + constructor(source: ViewGroup.LayoutParams) : super(source) {} + + constructor(source: ViewGroup.MarginLayoutParams) : super(source) {} + + companion object { + val FLAG_IS_OPENED = 0x1 + val FLAG_IS_OPENING = 0x2 + val FLAG_IS_CLOSING = 0x4 + } + } + + internal inner class AccessibilityDelegate : AccessibilityDelegateCompat() { + private val mTmpRect = Rect() + + override fun onInitializeAccessibilityNodeInfo(host: View, info: AccessibilityNodeInfoCompat) { + if (CAN_HIDE_DESCENDANTS) { + super.onInitializeAccessibilityNodeInfo(host, info) + } else { + // Obtain a node for the host, then manually generate the list + // of children to only include non-obscured views. + val superNode = AccessibilityNodeInfoCompat.obtain(info) + super.onInitializeAccessibilityNodeInfo(host, superNode) + + info.setSource(host) + val parent = ViewCompat.getParentForAccessibility(host) + if (parent is View) { + info.setParent(parent as View) + } + copyNodeInfoNoChildren(info, superNode) + superNode.recycle() + + addChildrenForAccessibility(info, host as ViewGroup) + } + + info.className = DrawerLayout::class.java.name + + // This view reports itself as focusable so that it can intercept + // the back button, but we should prevent this view from reporting + // itself as focusable to accessibility services. + info.isFocusable = false + info.isFocused = false + info.removeAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_FOCUS) + info.removeAction(AccessibilityNodeInfoCompat.AccessibilityActionCompat.ACTION_CLEAR_FOCUS) + } + + override fun onInitializeAccessibilityEvent(host: View, event: AccessibilityEvent) { + super.onInitializeAccessibilityEvent(host, event) + + event.className = DrawerLayout::class.java.name + } + + override fun dispatchPopulateAccessibilityEvent(host: View, event: AccessibilityEvent): Boolean { + // Special case to handle window state change events. As far as + // accessibility services are concerned, state changes from + // DrawerLayout invalidate the entire contents of the screen (like + // an Activity or Dialog) and they should announce the title of the + // new content. + if (event.eventType == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) { + val eventText = event.text + val visibleDrawer = findVisibleDrawer() + if (visibleDrawer != null) { + val edgeGravity = getDrawerViewAbsoluteGravity(visibleDrawer) + val title = getDrawerTitle(edgeGravity) + if (title != null) { + eventText.add(title) + } + } + + return true + } + + return super.dispatchPopulateAccessibilityEvent(host, event) + } + + override fun onRequestSendAccessibilityEvent(host: ViewGroup, child: View, + event: AccessibilityEvent): Boolean { + if (CAN_HIDE_DESCENDANTS || includeChildForAccessibility(child)) { + return super.onRequestSendAccessibilityEvent(host, child, event) + } + return false + } + + private fun addChildrenForAccessibility(info: AccessibilityNodeInfoCompat, v: ViewGroup) { + val childCount = v.childCount + for (i in 0..childCount - 1) { + val child = v.getChildAt(i) + if (includeChildForAccessibility(child)) { + info.addChild(child) + } + } + } + + /** + * This should really be in AccessibilityNodeInfoCompat, but there unfortunately + * seem to be a few elements that are not easily cloneable using the underlying API. + * Leave it private here as it's not general-purpose useful. + */ + private fun copyNodeInfoNoChildren(dest: AccessibilityNodeInfoCompat, + src: AccessibilityNodeInfoCompat) { + val rect = mTmpRect + + src.getBoundsInParent(rect) + dest.setBoundsInParent(rect) + + src.getBoundsInScreen(rect) + dest.setBoundsInScreen(rect) + + dest.isVisibleToUser = src.isVisibleToUser + dest.packageName = src.packageName + dest.className = src.className + dest.contentDescription = src.contentDescription + + dest.isEnabled = src.isEnabled + dest.isClickable = src.isClickable + dest.isFocusable = src.isFocusable + dest.isFocused = src.isFocused + dest.isAccessibilityFocused = src.isAccessibilityFocused + dest.isSelected = src.isSelected + dest.isLongClickable = src.isLongClickable + + dest.addAction(src.actions) + } + } + + internal inner class ChildAccessibilityDelegate : AccessibilityDelegateCompat() { + override fun onInitializeAccessibilityNodeInfo(child: View, + info: AccessibilityNodeInfoCompat) { + super.onInitializeAccessibilityNodeInfo(child, info) + + if (!includeChildForAccessibility(child)) { + // If we are ignoring the sub-tree rooted at the child, + // break the connection to the rest of the node tree. + // For details refer to includeChildForAccessibility. + info.setParent(null) + } + } + } + + companion object { + private val TAG = "DrawerLayout" + + /** + * Indicates that any drawers are in an idle, settled state. No animation is in progress. + */ + const val STATE_IDLE = ViewDragHelper.STATE_IDLE + + /** + * Indicates that a drawer is currently being dragged by the user. + */ + const val STATE_DRAGGING = ViewDragHelper.STATE_DRAGGING + + /** + * Indicates that a drawer is in the process of settling to a final position. + */ + const val STATE_SETTLING = ViewDragHelper.STATE_SETTLING + + /** + * The drawer is unlocked. + */ + const val LOCK_MODE_UNLOCKED = 0 + + /** + * The drawer is locked closed. The user may not open it, though + * the app may open it programmatically. + */ + const val LOCK_MODE_LOCKED_CLOSED = 1 + + /** + * The drawer is locked open. The user may not close it, though the app + * may close it programmatically. + */ + const val LOCK_MODE_LOCKED_OPEN = 2 + + /** + * The drawer's lock state is reset to default. + */ + const val LOCK_MODE_UNDEFINED = 3 + + + private val MIN_DRAWER_MARGIN = 64 // dp + private val DRAWER_ELEVATION = 10 //dp + + private val DEFAULT_SCRIM_COLOR = 0x99000000.toInt() + + /** + * Length of time to delay before peeking the drawer. + */ + private val PEEK_DELAY = 160 // ms + + /** + * Minimum velocity that will be detected as a fling + */ + private val MIN_FLING_VELOCITY = 400 // dips per second + + /** + * Experimental feature. + */ + private val ALLOW_EDGE_LOCK = false + + private val CHILDREN_DISALLOW_INTERCEPT = true + + private val TOUCH_SLOP_SENSITIVITY = 1f + + private val LAYOUT_ATTRS = intArrayOf(android.R.attr.layout_gravity) + + /** Whether we can use NO_HIDE_DESCENDANTS accessibility importance. */ + private val CAN_HIDE_DESCENDANTS = Build.VERSION.SDK_INT >= 19 + + /** Whether the drawer shadow comes from setting elevation on the drawer. */ + private val SET_DRAWER_SHADOW_FROM_ELEVATION = Build.VERSION.SDK_INT >= 21 + + internal val IMPL: DrawerLayoutCompatImpl + + init { + val version = Build.VERSION.SDK_INT + if (version >= 21) { + IMPL = DrawerLayoutCompatImplApi21() + } else { + IMPL = DrawerLayoutCompatImplBase() + } + } + + + + /** + * Simple gravity to string - only supports LEFT and RIGHT for debugging output. + + * @param gravity Absolute gravity value + * * + * @return LEFT or RIGHT as appropriate, or a hex string + */ + internal fun gravityToString(@EdgeGravity gravity: Int): String { + if (gravity and Gravity.LEFT == Gravity.LEFT) { + return "LEFT" + } + if (gravity and Gravity.RIGHT == Gravity.RIGHT) { + return "RIGHT" + } + return Integer.toHexString(gravity) + } + + private fun hasOpaqueBackground(v: View): Boolean { + val bg = v.background + if (bg != null) { + return bg.opacity == PixelFormat.OPAQUE + } + return false + } + + private fun includeChildForAccessibility(child: View): Boolean { + // If the child is not important for accessibility we make + // sure this hides the entire subtree rooted at it as the + // IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDATS is not + // supported on older platforms but we want to hide the entire + // content and not opened drawers if a drawer is opened. + return ViewCompat.getImportantForAccessibility(child) != ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS && ViewCompat.getImportantForAccessibility(child) != ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO + } + } +} +/** + * Open the specified drawer view by animating it into view. + + * @param drawerView Drawer view to open + */ +/** + * Open the specified drawer by animating it out of view. + + * @param gravity Gravity.LEFT to move the left drawer or Gravity.RIGHT for the right. + * * GravityCompat.START or GravityCompat.END may also be used. + */ +/** + * Close the specified drawer view by animating it into view. + + * @param drawerView Drawer view to close + */ +/** + * Close the specified drawer by animating it out of view. + + * @param gravity Gravity.LEFT to move the left drawer or Gravity.RIGHT for the right. + * * GravityCompat.START or GravityCompat.END may also be used. + */ \ No newline at end of file diff --git a/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/view/DrawerLayoutCompatApi21.kt b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/view/DrawerLayoutCompatApi21.kt new file mode 100644 index 0000000..902cdc0 --- /dev/null +++ b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/view/DrawerLayoutCompatApi21.kt @@ -0,0 +1,76 @@ +package com.loopeer.codereaderkt.ui.view + +import android.content.Context +import android.graphics.drawable.Drawable +import android.os.Build +import android.support.annotation.RequiresApi +import android.view.Gravity +import android.view.View +import android.view.ViewGroup +import android.view.WindowInsets + +internal object DrawerLayoutCompatApi21 { + + private val THEME_ATTRS = intArrayOf(android.R.attr.colorPrimaryDark) + + @RequiresApi(api = Build.VERSION_CODES.KITKAT_WATCH) + fun configureApplyInsets(drawerLayout: View) { + if (drawerLayout is DrawerLayoutImpl) { + drawerLayout.setOnApplyWindowInsetsListener(InsetsListener()) + drawerLayout.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_STABLE or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN + } + } + + @RequiresApi(api = Build.VERSION_CODES.KITKAT_WATCH) + fun dispatchChildInsets(child: View, insets: Any, gravity: Int) { + var wi = insets as WindowInsets + if (gravity == Gravity.LEFT) { + wi = wi.replaceSystemWindowInsets(wi.systemWindowInsetLeft, + wi.systemWindowInsetTop, 0, wi.systemWindowInsetBottom) + } else if (gravity == Gravity.RIGHT) { + wi = wi.replaceSystemWindowInsets(0, wi.systemWindowInsetTop, + wi.systemWindowInsetRight, wi.systemWindowInsetBottom) + } + child.dispatchApplyWindowInsets(wi) + } + + @RequiresApi(api = Build.VERSION_CODES.KITKAT_WATCH) + fun applyMarginInsets(lp: ViewGroup.MarginLayoutParams, insets: Any, + gravity: Int) { + var wi = insets as WindowInsets + if (gravity == Gravity.LEFT) { + wi = wi.replaceSystemWindowInsets(wi.systemWindowInsetLeft, + wi.systemWindowInsetTop, 0, wi.systemWindowInsetBottom) + } else if (gravity == Gravity.RIGHT) { + wi = wi.replaceSystemWindowInsets(0, wi.systemWindowInsetTop, + wi.systemWindowInsetRight, wi.systemWindowInsetBottom) + } + lp.leftMargin = wi.systemWindowInsetLeft + lp.topMargin = wi.systemWindowInsetTop + lp.rightMargin = wi.systemWindowInsetRight + lp.bottomMargin = wi.systemWindowInsetBottom + } + + @RequiresApi(api = Build.VERSION_CODES.KITKAT_WATCH) + fun getTopInset(insets: Any?): Int { + return if (insets != null) (insets as WindowInsets).systemWindowInsetTop else 0 + } + + fun getDefaultStatusBarBackground(context: Context): Drawable { + val a = context.obtainStyledAttributes(THEME_ATTRS) + try { + return a.getDrawable(0) + } finally { + a.recycle() + } + } + + @RequiresApi(api = Build.VERSION_CODES.KITKAT_WATCH) + internal class InsetsListener : View.OnApplyWindowInsetsListener { + override fun onApplyWindowInsets(v: View, insets: WindowInsets): WindowInsets { + val drawerLayout = v as DrawerLayoutImpl + drawerLayout.setChildInsets(insets, insets.systemWindowInsetTop > 0) + return insets.consumeSystemWindowInsets() + } + } +} \ No newline at end of file diff --git a/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/view/DrawerLayoutImpl.kt b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/view/DrawerLayoutImpl.kt new file mode 100644 index 0000000..1f13481 --- /dev/null +++ b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/view/DrawerLayoutImpl.kt @@ -0,0 +1,5 @@ +package com.loopeer.codereaderkt.ui.view + +internal interface DrawerLayoutImpl { + fun setChildInsets(insets: Any, drawStatusBar: Boolean) +} \ No newline at end of file diff --git a/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/view/ForegroundProgressRelativeLayout.kt b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/view/ForegroundProgressRelativeLayout.kt new file mode 100644 index 0000000..6731e9d --- /dev/null +++ b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/view/ForegroundProgressRelativeLayout.kt @@ -0,0 +1,112 @@ +package com.loopeer.codereaderkt.ui.view + +import android.animation.ValueAnimator +import android.content.Context +import android.graphics.Canvas +import android.graphics.Paint +import android.graphics.Rect +import android.support.v4.content.ContextCompat +import android.util.AttributeSet + +import com.loopeer.codereaderkt.R + +class ForegroundProgressRelativeLayout : ForegroundRelativeLayout { + + private var mRemainderPaint: Paint? = null + private var mProgressPaint: Paint? = null + + private var mProgressCurrent: Float = 0.toFloat() + private var mProgressPre: Float = 0.toFloat() + private var mProgressShow: Float = 0.toFloat() + private var mIsUnzip: Boolean = false + private var mRemainderColor: Int=0 + + constructor(context: Context) : super(context) {} + + @JvmOverloads constructor(context: Context, attrs: AttributeSet, defStyle: Int = 0) : super(context, attrs, defStyle) { + + val a = getContext().obtainStyledAttributes(attrs, R.styleable.ForegroundProgressRelativeLayout, + defStyle, 0) + mRemainderColor = a.getColor(R.styleable.ForegroundProgressRelativeLayout_remainderColor, ContextCompat.getColor(getContext(), R.color.repo_download_remainder_color)) + init() + setWillNotDraw(false) + } + + private fun init() { + sProgressTextPadding = resources.getDimensionPixelSize(R.dimen.inline_padding) + sUnzipTextPadding = resources.getDimensionPixelSize(R.dimen.medium_padding) + + mRemainderPaint = Paint() + mRemainderPaint!!.color = mRemainderColor + mRemainderPaint!!.style = Paint.Style.FILL + + mProgressPaint = Paint() + mProgressPaint!!.isAntiAlias = true + mProgressPaint!!.color = ContextCompat.getColor(context, R.color.colorPrimary) + mProgressPaint!!.style = Paint.Style.FILL + mProgressPaint!!.textSize = resources.getDimension(R.dimen.text_size_xxsmall) + } + + fun setProgressCurrent(i: Float) { + if (mProgressCurrent != i && i != 0f) { + mProgressPre = mProgressCurrent + mProgressCurrent = i + postProgressAnimation() + } else if (i == 0f) { + mProgressCurrent = i + mProgressShow = 0f + invalidate() + } + } + + fun setInitProgress(i: Float) { + mProgressPre = i + mProgressCurrent = i + mProgressShow = i + invalidate() + } + + private fun postProgressAnimation() { + val valueAnimator = ValueAnimator.ofFloat(0F, 1f) + valueAnimator.addUpdateListener { valueAnimator1 -> + val fraction = valueAnimator1.animatedFraction + mProgressShow = mProgressPre + fraction * (mProgressCurrent - mProgressPre) + invalidate() + } + valueAnimator.duration = 500 + valueAnimator.start() + } + + fun setUnzip(b: Boolean) { + mIsUnzip = b + if (mProgressCurrent == 1f) { + invalidate() + } + } + + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + + if (mProgressCurrent <= 1f) { + canvas.drawRect(width * mProgressShow, 0f, width.toFloat(), height.toFloat(), mRemainderPaint!!) + val content = String.format("%.0f", mProgressShow * 100) + "%" + val bounds = Rect() + mProgressPaint!!.getTextBounds(content, 0, content.length, bounds) + if (width * (1 - mProgressShow) > bounds.width()) { + canvas.drawText(content, width * mProgressShow + sProgressTextPadding, (height - sProgressTextPadding).toFloat(), mProgressPaint!!) + } + } + + if (mProgressCurrent == 1f && mIsUnzip) { + val content = resources.getString(R.string.repo_download_isunzip) + val bounds = Rect() + mProgressPaint!!.getTextBounds(content, 0, content.length, bounds) + canvas.drawText(content, (width - sUnzipTextPadding - bounds.width()).toFloat(), (height - sProgressTextPadding).toFloat(), mProgressPaint!!) + } + } + + companion object { + private var sProgressTextPadding: Int = 0 + private var sUnzipTextPadding: Int = 0 + } +} diff --git a/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/view/ForegroundRelativeLayout.kt b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/view/ForegroundRelativeLayout.kt new file mode 100644 index 0000000..ac4b031 --- /dev/null +++ b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/view/ForegroundRelativeLayout.kt @@ -0,0 +1,217 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.loopeer.codereaderkt.ui.view + +import android.annotation.TargetApi +import android.content.Context +import android.content.res.TypedArray +import android.graphics.Canvas +import android.graphics.Rect +import android.graphics.drawable.Drawable +import android.os.Build +import android.util.AttributeSet +import android.view.Gravity +import android.widget.RelativeLayout + +import com.loopeer.codereaderkt.R + +open class ForegroundRelativeLayout : RelativeLayout { + + private var mForeground: Drawable? = null + + private val mSelfBounds = Rect() + private val mOverlayBounds = Rect() + + private var mForegroundGravity = Gravity.FILL + + protected var mForegroundInPadding = true + + internal var mForegroundBoundsChanged = false + + constructor(context: Context) : super(context) {} + + @JvmOverloads constructor(context: Context, attrs: AttributeSet, defStyle: Int = 0) : super(context, attrs, defStyle) { + + val a = context.obtainStyledAttributes(attrs, R.styleable.ForegroundRelativeLayout, + defStyle, 0) + + mForegroundGravity = a.getInt( + R.styleable.ForegroundRelativeLayout_android_foregroundGravity, mForegroundGravity) + + val d = a.getDrawable(R.styleable.ForegroundRelativeLayout_android_foreground) + if (d != null) { + foreground = d + } + + mForegroundInPadding = a.getBoolean( + R.styleable.ForegroundRelativeLayout_android_foregroundInsidePadding, true) + + a.recycle() + } + + /** + * Describes how the foreground is positioned. + + * @return foreground gravity. + * * + * @see .setForegroundGravity + */ + override fun getForegroundGravity(): Int { + return mForegroundGravity + } + + /** + * Describes how the foreground is positioned. Defaults to START and TOP. + + * @param foregroundGravity See [Gravity] + * * + * @see .getForegroundGravity + */ + override fun setForegroundGravity(foregroundGravity: Int) { + var foregroundGravity = foregroundGravity + if (mForegroundGravity != foregroundGravity) { + if (foregroundGravity and Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK == 0) { + foregroundGravity = foregroundGravity or Gravity.START + } + + if (foregroundGravity and Gravity.VERTICAL_GRAVITY_MASK == 0) { + foregroundGravity = foregroundGravity or Gravity.TOP + } + + mForegroundGravity = foregroundGravity + + + if (mForegroundGravity == Gravity.FILL && mForeground != null) { + val padding = Rect() + mForeground!!.getPadding(padding) + } + + requestLayout() + } + } + + override fun verifyDrawable(who: Drawable): Boolean { + return super.verifyDrawable(who) || who === mForeground + } + + override fun jumpDrawablesToCurrentState() { + super.jumpDrawablesToCurrentState() + if (mForeground != null) mForeground!!.jumpToCurrentState() + } + + override fun drawableStateChanged() { + super.drawableStateChanged() + if (mForeground != null && mForeground!!.isStateful) { + mForeground!!.state = drawableState + } + } + + /** + * Supply a Drawable that is to be rendered on top of all of the child + * views in the frame layout. Any padding in the Drawable will be taken + * into account by ensuring that the children are inset to be placed + * inside of the padding area. + + * @param drawable The Drawable to be drawn on top of the children. + */ + override fun setForeground(drawable: Drawable?) { + if (mForeground !== drawable) { + if (mForeground != null) { + mForeground!!.callback = null + unscheduleDrawable(mForeground) + } + + mForeground = drawable + + if (drawable != null) { + setWillNotDraw(false) + drawable.callback = this + if (drawable.isStateful) { + drawable.state = drawableState + } + if (mForegroundGravity == Gravity.FILL) { + val padding = Rect() + drawable.getPadding(padding) + } + } else { + setWillNotDraw(true) + } + requestLayout() + invalidate() + } + } + + /** + * Returns the drawable used as the foreground of this FrameLayout. The + * foreground drawable, if non-null, is always drawn on top of the children. + + * @return A Drawable or null if no foreground was set. + */ + override fun getForeground(): Drawable? { + return mForeground + } + + override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { + super.onLayout(changed, left, top, right, bottom) + if (changed) + mForegroundBoundsChanged = changed + } + + override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { + super.onSizeChanged(w, h, oldw, oldh) + mForegroundBoundsChanged = true + } + + override fun draw(canvas: Canvas) { + super.draw(canvas) + + if (mForeground != null) { + val foreground = mForeground + + if (mForegroundBoundsChanged) { + mForegroundBoundsChanged = false + val selfBounds = mSelfBounds + val overlayBounds = mOverlayBounds + + val w = right - left + val h = bottom - top + + if (mForegroundInPadding) { + selfBounds.set(0, 0, w, h) + } else { + selfBounds.set(paddingLeft, paddingTop, + w - paddingRight, h - paddingBottom) + } + + Gravity.apply(mForegroundGravity, foreground!!.intrinsicWidth, + foreground.intrinsicHeight, selfBounds, overlayBounds) + foreground.bounds = overlayBounds + } + + foreground!!.draw(canvas) + } + } + + @TargetApi(Build.VERSION_CODES.LOLLIPOP) + override fun drawableHotspotChanged(x: Float, y: Float) { + super.drawableHotspotChanged(x, y) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { + if (mForeground != null) { + mForeground!!.setHotspot(x, y) + } + } + } +} diff --git a/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/view/ForegroundTextView.kt b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/view/ForegroundTextView.kt new file mode 100644 index 0000000..797b0c9 --- /dev/null +++ b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/view/ForegroundTextView.kt @@ -0,0 +1,111 @@ +/** + * Created by YuGang Yang on April 08, 2015. + * Copyright 2007-2015 Laputapp.com. All rights reserved. + */ +package com.loopeer.codereaderkt.ui.view + +import android.annotation.SuppressLint +import android.content.Context +import android.graphics.Canvas +import android.graphics.drawable.Drawable +import android.util.AttributeSet + +import com.loopeer.codereaderkt.R + +class ForegroundTextView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) : android.support.v7.widget.AppCompatTextView(context, attrs) { + + + private var mforeground: Drawable? = null + + init { + + val a = context.obtainStyledAttributes(attrs, R.styleable.ForegroundTextView) + val foreground = a.getDrawable(R.styleable.ForegroundTextView_android_foreground) + if (foreground != null) { + setForeground(foreground) + } + a.recycle() + } + + /** + * Supply a drawable resource that is to be rendered on top of all of the child + * views in the frame layout. + + * @param drawableResId The drawable resource to be drawn on top of the children. + */ + fun setForegroundResource(drawableResId: Int) { + setForeground(context.resources.getDrawable(drawableResId)) + } + + /** + * Supply a Drawable that is to be rendered on top of all of the child + * views in the frame layout. + + * @param drawable The Drawable to be drawn on top of the children. + */ + + + override fun setForeground(drawable: Drawable?) { + if (mforeground === drawable) { + return + } + if (mforeground != null) { + mforeground!!.callback = null + unscheduleDrawable(mforeground) + } + + mforeground = drawable + + if (drawable != null) { + drawable.callback = this + if (drawable.isStateful) { + drawable.state = drawableState + } + } + requestLayout() + invalidate() + } + + @SuppressLint("MissingSuperCall") + override fun verifyDrawable(who: Drawable): Boolean { + return super.verifyDrawable(who) || who === mforeground + } + + @SuppressLint("MissingSuperCall") + override fun jumpDrawablesToCurrentState() { + super.jumpDrawablesToCurrentState() + if (mforeground != null) mforeground!!.jumpToCurrentState() + } + + override fun drawableStateChanged() { + super.drawableStateChanged() + if (mforeground != null && mforeground!!.isStateful) { + mforeground!!.state = drawableState + } + } + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + if (mforeground != null) { + mforeground!!.setBounds(0, 0, measuredWidth, measuredHeight) + invalidate() + } + } + + override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { + super.onSizeChanged(w, h, oldw, oldh) + if (mforeground != null) { + mforeground!!.setBounds(0, 0, w, h) + invalidate() + } + } + + @SuppressLint("MissingSuperCall") + override fun draw(canvas: Canvas) { + super.draw(canvas) + + if (mforeground != null) { + mforeground!!.draw(canvas) + } + } +} diff --git a/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/view/LoginChecker.kt b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/view/LoginChecker.kt new file mode 100644 index 0000000..e6eefe5 --- /dev/null +++ b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/view/LoginChecker.kt @@ -0,0 +1,21 @@ +package com.loopeer.codereaderkt.ui.view + +import android.text.TextUtils + +class LoginChecker(checkObserver: Checker.CheckObserver) : Checker(checkObserver) { + + var username: String?=null + set(value) { + field=value + mCheckObserver.check(isEnable) + } + var password: String?=null + set(value) { + field=value + mCheckObserver.check(isEnable) + } + + + override val isEnable: Boolean + get() = !TextUtils.isEmpty(username) && !TextUtils.isEmpty(password) +} diff --git a/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/view/NestHorizontalScrollView.kt b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/view/NestHorizontalScrollView.kt new file mode 100644 index 0000000..76a1976 --- /dev/null +++ b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/view/NestHorizontalScrollView.kt @@ -0,0 +1,1754 @@ +package com.loopeer.codereaderkt.ui.view + +import android.annotation.SuppressLint +import android.content.Context +import android.content.res.TypedArray +import android.graphics.Canvas +import android.graphics.Rect +import android.os.Bundle +import android.os.Parcel +import android.os.Parcelable +import android.support.v4.view.AccessibilityDelegateCompat +import android.support.v4.view.InputDeviceCompat +import android.support.v4.view.MotionEventCompat +import android.support.v4.view.NestedScrollingChild +import android.support.v4.view.NestedScrollingChildHelper +import android.support.v4.view.NestedScrollingParent +import android.support.v4.view.NestedScrollingParentHelper +import android.support.v4.view.VelocityTrackerCompat +import android.support.v4.view.ViewCompat +import android.support.v4.view.accessibility.AccessibilityEventCompat +import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat +import android.support.v4.view.accessibility.AccessibilityRecordCompat +import android.support.v4.widget.EdgeEffectCompat +import android.support.v4.widget.ScrollerCompat +import android.util.AttributeSet +import android.util.Log +import android.util.TypedValue +import android.view.FocusFinder +import android.view.KeyEvent +import android.view.MotionEvent +import android.view.VelocityTracker +import android.view.View +import android.view.ViewConfiguration +import android.view.ViewGroup +import android.view.ViewParent +import android.view.accessibility.AccessibilityEvent +import android.view.animation.AnimationUtils +import android.widget.FrameLayout +import android.widget.ScrollView + +class NestHorizontalScrollView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : FrameLayout(context, attrs, defStyleAttr), NestedScrollingParent, NestedScrollingChild { + + private var mLastScroll: Long = 0 + + private val mTempRect = Rect() + private var mScroller: ScrollerCompat? = null + private var mEdgeGlowLeft: EdgeEffectCompat? = null + private var mEdgeGlowRight: EdgeEffectCompat? = null + + /** + * Position of the last motion event. + */ + private var mLastMotionX: Int = 0 + private var mLastMotionY: Int = 0 + + /** + * True when the layout has changed but the traversal has not come through yet. + * Ideally the view hierarchy would keep track of this for us. + */ + private var mIsLayoutDirty = true + private var mIsLaidOut = false + + /** + * The child to give focus to in the event that a child has requested focus while the + * layout is dirty. This prevents the scroll from being wrong if the child has not been + * laid out before requesting focus. + */ + private var mChildToScrollTo: View? = null + + /** + * True if the user is currently dragging this ScrollView around. This is + * not the same as 'is being flinged', which can be checked by + * mScroller.isFinished() (flinging begins when the user lifts his finger). + */ + private var mIsBeingDragged = false + + /** + * Determines speed during touch scrolling + */ + private var mVelocityTracker: VelocityTracker? = null + + /** + * When set to true, the scroll view measure its child to make it fill the currently + * visible area. + */ + /** + * Indicates whether this ScrollView's content is stretched to fill the viewport. + + * @return True if the content fills the viewport, false otherwise. + * * + * * + * @attr ref android.R.styleable#ScrollView_fillViewport + */ + /** + * Indicates this ScrollView whether it should stretch its content height to fill + * the viewport or not. + + * @param fillViewport True to stretch the content's height to the viewport's + * * boundaries, false otherwise. + * * + * * + * @attr ref android.R.styleable#ScrollView_fillViewport + */ + var isFillViewport: Boolean = false + set(fillViewport) { + if (fillViewport != isFillViewport) { + field = fillViewport + requestLayout() + } + } + + /** + * Whether arrow scrolling is animated. + */ + /** + * @return Whether arrow scrolling will animate its transition. + */ + /** + * Set whether arrow scrolling will animate its transition. + * @param smoothScrollingEnabled whether arrow scrolling will animate its transition + */ + var isSmoothScrollingEnabled = true + + private var mTouchSlop: Int = 0 + private var mMinimumVelocity: Int = 0 + private var mMaximumVelocity: Int = 0 + + /** + * ID of the active pointer. This is used to retain consistency during + * drags/flings if multiple pointers are used. + */ + private var mActivePointerId = INVALID_POINTER + + /** + * Used during scrolling to retrieve the new offset within the window. + */ + private val mScrollOffset = IntArray(2) + private val mScrollConsumed = IntArray(2) + private var mNestedXOffset: Int = 0 + + private var mSavedState: SavedState? = null + + private val mParentHelper: NestedScrollingParentHelper + private val mChildHelper: NestedScrollingChildHelper + + private var mHorizontalScrollFactor: Float = 0.toFloat() + + init { + initScrollView() + + val a = context.obtainStyledAttributes( + attrs, SCROLLVIEW_STYLEABLE, defStyleAttr, 0) + + isFillViewport = a.getBoolean(0, false) + + a.recycle() + + mParentHelper = NestedScrollingParentHelper(this) + mChildHelper = NestedScrollingChildHelper(this) + + // ...because why else would you be using this widget? + isNestedScrollingEnabled = true + + ViewCompat.setAccessibilityDelegate(this, ACCESSIBILITY_DELEGATE) + } + + // NestedScrollingChild + + override fun setNestedScrollingEnabled(enabled: Boolean) { + mChildHelper.isNestedScrollingEnabled = enabled + } + + override fun isNestedScrollingEnabled(): Boolean { + return mChildHelper.isNestedScrollingEnabled + } + + override fun startNestedScroll(axes: Int): Boolean { + return mChildHelper.startNestedScroll(axes) + } + + override fun stopNestedScroll() { + mChildHelper.stopNestedScroll() + } + + override fun hasNestedScrollingParent(): Boolean { + return mChildHelper.hasNestedScrollingParent() + } + + override fun dispatchNestedScroll(dxConsumed: Int, dyConsumed: Int, dxUnconsumed: Int, + dyUnconsumed: Int, offsetInWindow: IntArray?): Boolean { + return mChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, + offsetInWindow) + } + + override fun dispatchNestedPreScroll(dx: Int, dy: Int, consumed: IntArray?, offsetInWindow: IntArray?): Boolean { + return mChildHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow) + } + + override fun dispatchNestedFling(velocityX: Float, velocityY: Float, consumed: Boolean): Boolean { + return mChildHelper.dispatchNestedFling(velocityX, velocityY, consumed) + } + + override fun dispatchNestedPreFling(velocityX: Float, velocityY: Float): Boolean { + return mChildHelper.dispatchNestedPreFling(velocityX, velocityY) + } + + // NestedScrollingParent + + override fun onStartNestedScroll(child: View, target: View, nestedScrollAxes: Int): Boolean { + return nestedScrollAxes and ViewCompat.SCROLL_AXIS_HORIZONTAL != 0 + } + + override fun onNestedScrollAccepted(child: View, target: View, nestedScrollAxes: Int) { + mParentHelper.onNestedScrollAccepted(child, target, nestedScrollAxes) + startNestedScroll(ViewCompat.SCROLL_AXIS_HORIZONTAL) + } + + override fun onStopNestedScroll(target: View) { + stopNestedScroll() + } + + override fun onNestedScroll(target: View, dxConsumed: Int, dyConsumed: Int, dxUnconsumed: Int, + dyUnconsumed: Int) { + val oldScrollX = scrollX + scrollBy(dxUnconsumed, 0) + val mxConsumed = scrollX - oldScrollX + val mxUnconsumed = dxUnconsumed - mxConsumed + dispatchNestedScroll(mxConsumed, 0, mxUnconsumed, 0, null) + } + + override fun onNestedPreScroll(target: View, dx: Int, dy: Int, consumed: IntArray) { + // Do nothing + } + + override fun onNestedFling(target: View, velocityX: Float, velocityY: Float, consumed: Boolean): Boolean { + if (!consumed) { + flingWithNestedDispatch(velocityX.toInt()) + return true + } + return false + } + + override fun onNestedPreFling(target: View, velocityX: Float, velocityY: Float): Boolean { + // Do nothing + return false + } + + override fun getNestedScrollAxes(): Int { + return mParentHelper.nestedScrollAxes + } + + // ScrollView import + + override fun shouldDelayChildPressedState(): Boolean { + return true + } + + override fun getLeftFadingEdgeStrength(): Float { + if (childCount == 0) { + return 0.0f + } + + val length = horizontalFadingEdgeLength + val scrollX = scrollX + if (scrollX < length) { + return scrollX / length.toFloat() + } + + return 1.0f + } + + override fun getRightFadingEdgeStrength(): Float { + if (childCount == 0) { + return 0.0f + } + + val length = horizontalFadingEdgeLength + val rightEdge = width - paddingRight + val span = getChildAt(0).right - scrollX - rightEdge + if (span < length) { + return span / length.toFloat() + } + + return 1.0f + } + + /** + * @return The maximum amount this scroll view will scroll in response to + * * an arrow event. + */ + val maxScrollAmount: Int + get() = (MAX_SCROLL_FACTOR * width).toInt() + + private fun initScrollView() { + mScroller = ScrollerCompat.create(context, null) + isFocusable = true + descendantFocusability = ViewGroup.FOCUS_AFTER_DESCENDANTS + setWillNotDraw(false) + val configuration = ViewConfiguration.get(context) + mTouchSlop = configuration.scaledTouchSlop + mMinimumVelocity = configuration.scaledMinimumFlingVelocity + mMaximumVelocity = configuration.scaledMaximumFlingVelocity + } + + override fun addView(child: View) { + if (childCount > 0) { + throw IllegalStateException("ScrollView can host only one direct child") + } + + super.addView(child) + } + + override fun addView(child: View, index: Int) { + if (childCount > 0) { + throw IllegalStateException("ScrollView can host only one direct child") + } + + super.addView(child, index) + } + + override fun addView(child: View, params: ViewGroup.LayoutParams) { + if (childCount > 0) { + throw IllegalStateException("ScrollView can host only one direct child") + } + + super.addView(child, params) + } + + override fun addView(child: View, index: Int, params: ViewGroup.LayoutParams) { + if (childCount > 0) { + throw IllegalStateException("ScrollView can host only one direct child") + } + + super.addView(child, index, params) + } + + /** + * @return Returns true this ScrollView can be scrolled + */ + private fun canScroll(): Boolean { + val child = getChildAt(0) + if (child != null) { + val childWidth = child.width + return width < childWidth + paddingRight + paddingLeft + } + return false + } + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + + if (!isFillViewport) { + return + } + + val heightMode = View.MeasureSpec.getMode(heightMeasureSpec) + if (heightMode == View.MeasureSpec.UNSPECIFIED) { + return + } + + if (childCount > 0) { + val child = getChildAt(0) + var width = measuredWidth + if (child.measuredWidth < width) { + val lp = child.layoutParams as FrameLayout.LayoutParams + + val childHeightMeasureSpec = ViewGroup.getChildMeasureSpec(heightMeasureSpec, + paddingTop + paddingBottom, lp.height) + width -= paddingRight + width -= paddingLeft + val childWidthMeasureSpec = View.MeasureSpec.makeMeasureSpec(width, View.MeasureSpec.EXACTLY) + + child.measure(childWidthMeasureSpec, childHeightMeasureSpec) + } + } + } + + override fun dispatchKeyEvent(event: KeyEvent): Boolean { + // Let the focused view and/or our descendants get the key first + return super.dispatchKeyEvent(event) || executeKeyEvent(event) + } + + /** + * You can call this function yourself to have the scroll view perform + * scrolling from a key event, just as if the event had been dispatched to + * it by the view hierarchy. + + * @param event The key event to execute. + * * + * @return Return true if the event was handled, else false. + */ + fun executeKeyEvent(event: KeyEvent): Boolean { + mTempRect.setEmpty() + + if (!canScroll()) { + if (isFocused && event.keyCode != KeyEvent.KEYCODE_BACK) { + var currentFocused: View? = findFocus() + if (currentFocused === this) currentFocused = null + val nextFocused = FocusFinder.getInstance().findNextFocus(this, + currentFocused, View.FOCUS_RIGHT) + return nextFocused != null + && nextFocused !== this + && nextFocused.requestFocus(View.FOCUS_RIGHT) + } + return false + } + + var handled = false + if (event.action == KeyEvent.ACTION_DOWN) { + when (event.keyCode) { + KeyEvent.KEYCODE_DPAD_LEFT -> if (!event.isAltPressed) { + handled = arrowScroll(View.FOCUS_LEFT) + } else { + handled = fullScroll(View.FOCUS_LEFT) + } + KeyEvent.KEYCODE_DPAD_RIGHT -> if (!event.isAltPressed) { + handled = arrowScroll(View.FOCUS_RIGHT) + } else { + handled = fullScroll(View.FOCUS_RIGHT) + } + KeyEvent.KEYCODE_SPACE -> pageScroll(if (event.isShiftPressed) View.FOCUS_LEFT else View.FOCUS_RIGHT) + } + } + + return handled + } + + private fun inChild(x: Int, y: Int): Boolean { + if (childCount > 0) { + val scrollX = scrollX + val child = getChildAt(0) + return !(x < child.left - scrollX + || x >= child.right - scrollX + || y < child.top + || y >= child.bottom) + } + return false + } + + private fun initOrResetVelocityTracker() { + if (mVelocityTracker == null) { + mVelocityTracker = VelocityTracker.obtain() + } else { + mVelocityTracker!!.clear() + } + } + + private fun initVelocityTrackerIfNotExists() { + if (mVelocityTracker == null) { + mVelocityTracker = VelocityTracker.obtain() + } + } + + private fun recycleVelocityTracker() { + if (mVelocityTracker != null) { + mVelocityTracker!!.recycle() + mVelocityTracker = null + } + } + + override fun requestDisallowInterceptTouchEvent(disallowIntercept: Boolean) { + if (disallowIntercept) { + recycleVelocityTracker() + } + super.requestDisallowInterceptTouchEvent(disallowIntercept) + } + + + override fun onInterceptTouchEvent(ev: MotionEvent): Boolean { + /* + * This method JUST determines whether we want to intercept the motion. + * If we return true, onMotionEvent will be called and we do the actual + * scrolling there. + */ + + /* + * Shortcut the most recurring case: the user is in the dragging + * state and he is moving his finger. We want to intercept this + * motion. + */ + val action = ev.action + if (action == MotionEvent.ACTION_MOVE && mIsBeingDragged) { + return true + } + + when (action and MotionEventCompat.ACTION_MASK) { + MotionEvent.ACTION_MOVE -> { + /* + * mIsBeingDragged == false, otherwise the shortcut would have caught it. Check + * whether the user has moved far enough from his original down touch. + */ + + /* + * Locally do absolute value. mLastMotionY is set to the y value + * of the down event. + */ + val activePointerId = mActivePointerId + if (activePointerId == INVALID_POINTER) { + // If we don't have a valid id, the touch down wasn't on content. + } + + val pointerIndex = MotionEventCompat.findPointerIndex(ev, activePointerId) + if (pointerIndex == -1) { + Log.e(TAG, "Invalid pointerId=" + activePointerId + + " in onInterceptTouchEvent") + } + + val x = MotionEventCompat.getX(ev, pointerIndex).toInt() + val y = MotionEventCompat.getY(ev, pointerIndex).toInt() + val xDiff = Math.abs(x - mLastMotionX) + val yDiff = Math.abs(y - mLastMotionY) + if (xDiff > mTouchSlop && xDiff > yDiff + && nestedScrollAxes and ViewCompat.SCROLL_AXIS_HORIZONTAL == 0) { + mIsBeingDragged = true + mLastMotionX = x + mLastMotionY = y + initVelocityTrackerIfNotExists() + mVelocityTracker!!.addMovement(ev) + mNestedXOffset = 0 + val parent = parent + parent?.requestDisallowInterceptTouchEvent(true) + } else { + mIsBeingDragged = false + } + } + + MotionEvent.ACTION_DOWN -> { + val x = ev.x.toInt() + val y = ev.y.toInt() + if (!inChild(x, y)) { + mIsBeingDragged = false + recycleVelocityTracker() + } + + /* + * Remember location of down touch. + * ACTION_DOWN always refers to pointer index 0. + */ + mLastMotionX = x + mLastMotionY = y + mActivePointerId = MotionEventCompat.getPointerId(ev, 0) + + initOrResetVelocityTracker() + mVelocityTracker!!.addMovement(ev) + /* + * If being flinged and user touches the screen, initiate drag; + * otherwise don't. mScroller.isFinished should be false when + * being flinged. + */ + mIsBeingDragged = !mScroller!!.isFinished + startNestedScroll(ViewCompat.SCROLL_AXIS_HORIZONTAL) + } + + MotionEvent.ACTION_CANCEL, MotionEvent.ACTION_UP -> { + /* Release the drag */ + mIsBeingDragged = false + mActivePointerId = INVALID_POINTER + recycleVelocityTracker() + stopNestedScroll() + } + MotionEventCompat.ACTION_POINTER_UP -> onSecondaryPointerUp(ev) + } + + /* + * The only time we want to intercept motion events is if we are in the + * drag mode. + */ + return mIsBeingDragged + } + + override fun onTouchEvent(ev: MotionEvent): Boolean { + initVelocityTrackerIfNotExists() + + val vtev = MotionEvent.obtain(ev) + + val actionMasked = MotionEventCompat.getActionMasked(ev) + + if (actionMasked == MotionEvent.ACTION_DOWN) { + mNestedXOffset = 0 + } + vtev.offsetLocation(mNestedXOffset.toFloat(), 0f) + + when (actionMasked) { + MotionEvent.ACTION_DOWN -> { + if (childCount == 0) { + return false + } + if (mIsBeingDragged == !mScroller!!.isFinished) { + val parent = parent + parent?.requestDisallowInterceptTouchEvent(true) + } + + /* + * If being flinged and user touches, stop the fling. isFinished + * will be false if being flinged. + */ + if (!mScroller!!.isFinished) { + mScroller!!.abortAnimation() + } + + // Remember where the motion event started + mLastMotionX = ev.x.toInt() + mActivePointerId = MotionEventCompat.getPointerId(ev, 0) + startNestedScroll(ViewCompat.SCROLL_AXIS_HORIZONTAL) + } + MotionEvent.ACTION_MOVE -> { + val activePointerIndex = MotionEventCompat.findPointerIndex(ev, + mActivePointerId) + if (activePointerIndex == -1) { + Log.e(TAG, "Invalid pointerId=$mActivePointerId in onTouchEvent") + } + + val x = MotionEventCompat.getX(ev, activePointerIndex).toInt() + var deltaX = mLastMotionX - x + if (dispatchNestedPreScroll(deltaX, 0, mScrollConsumed, mScrollOffset)) { + deltaX -= mScrollConsumed[0] + vtev.offsetLocation(mScrollOffset[0].toFloat(), 0f) + mNestedXOffset += mScrollOffset[0] + } + if (!mIsBeingDragged && Math.abs(deltaX) > mTouchSlop) { + val parent = parent + parent?.requestDisallowInterceptTouchEvent(true) + mIsBeingDragged = true + if (deltaX > 0) { + deltaX -= mTouchSlop + } else { + deltaX += mTouchSlop + } + } + if (mIsBeingDragged) { + // Scroll to follow the motion event + mLastMotionX = x - mScrollOffset[0] + + val oldX = scrollX + val range = scrollRange + val overscrollMode = ViewCompat.getOverScrollMode(this) + val canOverscroll = overscrollMode == ViewCompat.OVER_SCROLL_ALWAYS || overscrollMode == ViewCompat.OVER_SCROLL_IF_CONTENT_SCROLLS && range > 0 + + // Calling overScrollByCompat will call onOverScrolled, which + // calls onScrollChanged if applicable. + if (overScrollByCompat(deltaX, 0, scrollX, 0, range, 0, 0, + 0, true) && !hasNestedScrollingParent()) { + // Break our velocity if we hit a scroll barrier. + mVelocityTracker!!.clear() + } + + val scrolledDeltaX = scrollX - oldX + val unconsumedX = deltaX - scrolledDeltaX + if (dispatchNestedScroll(scrolledDeltaX, 0, unconsumedX, 0, mScrollOffset)) { + mLastMotionX -= mScrollOffset[0] + vtev.offsetLocation(mScrollOffset[0].toFloat(), 0f) + mNestedXOffset += mScrollOffset[0] + } else if (canOverscroll) { + ensureGlows() + val pulledToX = oldX + deltaX + if (pulledToX < 0) { + mEdgeGlowLeft!!.onPull(deltaX.toFloat() / width, + MotionEventCompat.getY(ev, activePointerIndex) / height) + if (!mEdgeGlowRight!!.isFinished) { + mEdgeGlowRight!!.onRelease() + } + } else if (pulledToX > range) { + mEdgeGlowRight!!.onPull(deltaX.toFloat() / width, + 1f - MotionEventCompat.getY(ev, activePointerIndex) / height) + if (!mEdgeGlowLeft!!.isFinished) { + mEdgeGlowLeft!!.onRelease() + } + } + if (mEdgeGlowLeft != null && (!mEdgeGlowLeft!!.isFinished || !mEdgeGlowRight!!.isFinished)) { + ViewCompat.postInvalidateOnAnimation(this) + } + } + } + } + MotionEvent.ACTION_UP -> if (mIsBeingDragged) { + val velocityTracker = mVelocityTracker + velocityTracker!!.computeCurrentVelocity(1000, mMaximumVelocity.toFloat()) + val initialVelocity = VelocityTrackerCompat.getXVelocity(velocityTracker, + mActivePointerId).toInt() + + if (Math.abs(initialVelocity) > mMinimumVelocity) { + flingWithNestedDispatch(-initialVelocity) + } + + mActivePointerId = INVALID_POINTER + endDrag() + } + MotionEvent.ACTION_CANCEL -> if (mIsBeingDragged && childCount > 0) { + mActivePointerId = INVALID_POINTER + endDrag() + } + MotionEventCompat.ACTION_POINTER_DOWN -> { + val index = MotionEventCompat.getActionIndex(ev) + mLastMotionX = MotionEventCompat.getX(ev, index).toInt() + mActivePointerId = MotionEventCompat.getPointerId(ev, index) + } + MotionEventCompat.ACTION_POINTER_UP -> { + onSecondaryPointerUp(ev) + mLastMotionX = MotionEventCompat.getX(ev, + MotionEventCompat.findPointerIndex(ev, mActivePointerId)).toInt() + } + } + + if (mVelocityTracker != null) { + mVelocityTracker!!.addMovement(vtev) + } + vtev.recycle() + return true + } + + private fun onSecondaryPointerUp(ev: MotionEvent) { + val pointerIndex = ev.action and MotionEventCompat.ACTION_POINTER_INDEX_MASK shr MotionEventCompat.ACTION_POINTER_INDEX_SHIFT + val pointerId = MotionEventCompat.getPointerId(ev, pointerIndex) + if (pointerId == mActivePointerId) { + // This was our active pointer going up. Choose a new + // active pointer and adjust accordingly. + // TODO: Make this decision more intelligent. + val newPointerIndex = if (pointerIndex == 0) 1 else 0 + mLastMotionX = MotionEventCompat.getX(ev, newPointerIndex).toInt() + mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex) + if (mVelocityTracker != null) { + mVelocityTracker!!.clear() + } + } + } + + override fun onGenericMotionEvent(event: MotionEvent): Boolean { + if (MotionEventCompat.getSource(event) and InputDeviceCompat.SOURCE_CLASS_POINTER != 0) { + when (event.action) { + MotionEventCompat.ACTION_SCROLL -> { + if (!mIsBeingDragged) { + val hscroll = MotionEventCompat.getAxisValue(event, + MotionEventCompat.AXIS_HSCROLL) + if (hscroll != 0f) { + val delta = (hscroll * horizontalScrollFactorCompat).toInt() + val range = scrollRange + val oldScrollX = scrollX + var newScrollX = oldScrollX - delta + if (newScrollX < 0) { + newScrollX = 0 + } else if (newScrollX > range) { + newScrollX = range + } + if (newScrollX != oldScrollX) { + super.scrollTo(newScrollX, scrollY) + return true + } + } + } + } + } + } + return false + } + + private val horizontalScrollFactorCompat: Float + get() { + if (mHorizontalScrollFactor == 0f) { + val outValue = TypedValue() + val context = context + if (!context.theme.resolveAttribute( + android.R.attr.listPreferredItemHeight, outValue, true)) { + throw IllegalStateException( + "Expected theme to define listPreferredItemHeight.") + } + mHorizontalScrollFactor = outValue.getDimension( + context.resources.displayMetrics) + } + return mHorizontalScrollFactor + } + + override fun onOverScrolled(scrollX: Int, scrollY: Int, + clampedX: Boolean, clampedY: Boolean) { + super.scrollTo(scrollX, scrollY) + } + + internal fun overScrollByCompat(deltaX: Int, deltaY: Int, + scrollX: Int, scrollY: Int, + scrollRangeX: Int, scrollRangeY: Int, + maxOverScrollX: Int, maxOverScrollY: Int, + isTouchEvent: Boolean): Boolean { + var maxOverScrollX = maxOverScrollX + var maxOverScrollY = maxOverScrollY + val overScrollMode = ViewCompat.getOverScrollMode(this) + val canScrollHorizontal = computeHorizontalScrollRange() > computeHorizontalScrollExtent() + val canScrollVertical = computeVerticalScrollRange() > computeVerticalScrollExtent() + val overScrollHorizontal = overScrollMode == ViewCompat.OVER_SCROLL_ALWAYS || overScrollMode == ViewCompat.OVER_SCROLL_IF_CONTENT_SCROLLS && canScrollHorizontal + val overScrollVertical = overScrollMode == ViewCompat.OVER_SCROLL_ALWAYS || overScrollMode == ViewCompat.OVER_SCROLL_IF_CONTENT_SCROLLS && canScrollVertical + + var newScrollX = scrollX + deltaX + if (!overScrollHorizontal) { + maxOverScrollX = 0 + } + + var newScrollY = scrollY + deltaY + if (!overScrollVertical) { + maxOverScrollY = 0 + } + + // Clamp values if at the limits and record + val left = -maxOverScrollX + val right = maxOverScrollX + scrollRangeX + val top = -maxOverScrollY + val bottom = maxOverScrollY + scrollRangeY + + var clampedX = false + if (newScrollX > right) { + newScrollX = right + clampedX = true + } else if (newScrollX < left) { + newScrollX = left + clampedX = true + } + + var clampedY = false + if (newScrollY > bottom) { + newScrollY = bottom + clampedY = true + } else if (newScrollY < top) { + newScrollY = top + clampedY = true + } + + onOverScrolled(newScrollX, newScrollY, clampedX, clampedY) + + return clampedX || clampedY + } + + private val scrollRange: Int + get() { + var scrollRange = 0 + if (childCount > 0) { + val child = getChildAt(0) + scrollRange = Math.max(0, + child.width - (width - paddingLeft - paddingRight)) + } + return scrollRange + } + + /** + * + * + * Finds the next focusable component that fits in the specified bounds. + * + + * @param topFocus look for a candidate is the one at the top of the bounds + * * if topFocus is true, or at the bottom of the bounds if topFocus is + * * false + * * + * @param left the left offset of the bounds in which a focusable must be + * * found + * * + * @param right the right offset of the bounds in which a focusable must + * * be found + * * + * @return the next focusable component in the bounds or null if none can + * * be found + */ + private fun findFocusableViewInBounds(topFocus: Boolean, left: Int, right: Int): View { + + val focusables = getFocusables(View.FOCUS_FORWARD) + var focusCandidate: View? = null + + /* + * A fully contained focusable is one where its top is below the bound's + * top, and its bottom is above the bound's bottom. A partially + * contained focusable is one where some part of it is within the + * bounds, but it also has some part that is not within bounds. A fully contained + * focusable is preferred to a partially contained focusable. + */ + var foundFullyContainedFocusable = false + + val count = focusables.size + for (i in 0..count - 1) { + val view = focusables[i] + val viewLeft = view.left + val viewRight = view.right + + if (left < viewRight && viewLeft < right) { + /* + * the focusable is in the target area, it is a candidate for + * focusing + */ + + val viewIsFullyContained = left < viewLeft && viewRight < right + + if (focusCandidate == null) { + /* No candidate, take this one */ + focusCandidate = view + foundFullyContainedFocusable = viewIsFullyContained + } else { + val viewIsCloserToBoundary = topFocus && viewLeft < focusCandidate.left || !topFocus && viewRight > focusCandidate + .right + + if (foundFullyContainedFocusable) { + if (viewIsFullyContained && viewIsCloserToBoundary) { + /* + * We're dealing with only fully contained views, so + * it has to be closer to the boundary to beat our + * candidate + */ + focusCandidate = view + } + } else { + if (viewIsFullyContained) { + /* Any fully contained view beats a partially contained view */ + focusCandidate = view + foundFullyContainedFocusable = true + } else if (viewIsCloserToBoundary) { + /* + * Partially contained view beats another partially + * contained view if it's closer + */ + focusCandidate = view + } + } + } + } + } + + return focusCandidate!! + } + + /** + * + * Handles scrolling in response to a "page up/down" shortcut press. This + * method will scroll the view by one page up or down and give the focus + * to the topmost/bottommost component in the new visible area. If no + * component is a good candidate for focus, this scrollview reclaims the + * focus. + + * @param direction the scroll direction: [View.FOCUS_UP] + * * to go one page up or + * * [View.FOCUS_RIGHT] to go one page down + * * + * @return true if the key event is consumed by this method, false otherwise + */ + fun pageScroll(direction: Int): Boolean { + val toRight = direction == View.FOCUS_RIGHT + val width = width + + if (toRight) { + mTempRect.left = scrollX + width + val count = childCount + if (count > 0) { + val view = getChildAt(count - 1) + if (mTempRect.left + width > view.right) { + mTempRect.left = view.right - width + } + } + } else { + mTempRect.left = scrollX - width + if (mTempRect.left < 0) { + mTempRect.left = 0 + } + } + mTempRect.right = mTempRect.left + width + + return scrollAndFocus(direction, mTempRect.left, mTempRect.right) + } + + /** + * + * Handles scrolling in response to a "home/end" shortcut press. This + * method will scroll the view to the top or bottom and give the focus + * to the topmost/bottommost component in the new visible area. If no + * component is a good candidate for focus, this scrollview reclaims the + * focus. + + * @param direction the scroll direction: [View.FOCUS_UP] + * * to go the top of the view or + * * [View.FOCUS_DOWN] to go the bottom + * * + * @return true if the key event is consumed by this method, false otherwise + */ + fun fullScroll(direction: Int): Boolean { + val right = direction == View.FOCUS_RIGHT + val width = width + + mTempRect.left = 0 + mTempRect.right = width + + if (right) { + val count = childCount + if (count > 0) { + val view = getChildAt(count - 1) + mTempRect.right = view.right + paddingRight + mTempRect.left = mTempRect.right - width + } + } + + return scrollAndFocus(direction, mTempRect.left, mTempRect.right) + } + + /** + * + * Scrolls the view to make the area defined by `top` and + * `bottom` visible. This method attempts to give the focus + * to a component visible in this area. If no component can be focused in + * the new visible area, the focus is reclaimed by this ScrollView. + + * @param direction the scroll direction: [View.FOCUS_UP] + * * to go upward, [View.FOCUS_DOWN] to downward + * * + * @param left the left offset of the new area to be made visible + * * + * @param right the right offset of the new area to be made visible + * * + * @return true if the key event is consumed by this method, false otherwise + */ + private fun scrollAndFocus(direction: Int, left: Int, right: Int): Boolean { + var handled = true + + val width = width + val containerLeft = scrollX + val containerRight = containerLeft + width + val toLeft = direction == View.FOCUS_LEFT + + var newFocused: View? = findFocusableViewInBounds(toLeft, left, right) + if (newFocused == null) { + newFocused = this + } + + if (left >= containerLeft && right <= containerRight) { + handled = false + } else { + val delta = if (toLeft) left - containerLeft else right - containerRight + doScrollX(delta) + } + + if (newFocused !== findFocus()) newFocused.requestFocus(direction) + + return handled + } + + /** + * Handle scrolling in response to an up or down arrow click. + + * @param direction The direction corresponding to the arrow key that was + * * pressed + * * + * @return True if we consumed the event, false otherwise + */ + fun arrowScroll(direction: Int): Boolean { + + var currentFocused: View? = findFocus() + if (currentFocused === this) currentFocused = null + + val nextFocused = FocusFinder.getInstance().findNextFocus(this, currentFocused, direction) + + val maxJump = maxScrollAmount + + if (nextFocused != null && isWithinDeltaOfScreen(nextFocused, maxJump, height)) { + nextFocused.getDrawingRect(mTempRect) + offsetDescendantRectToMyCoords(nextFocused, mTempRect) + val scrollDelta = computeScrollDeltaToGetChildRectOnScreen(mTempRect) + doScrollX(scrollDelta) + nextFocused.requestFocus(direction) + } else { + // no new focus + var scrollDelta = maxJump + + if (direction == View.FOCUS_LEFT && scrollX < scrollDelta) { + scrollDelta = scrollX + } else if (direction == View.FOCUS_RIGHT) { + if (childCount > 0) { + val daRight = getChildAt(0).right + val screenRight = scrollX + width - paddingRight + if (daRight - screenRight < maxJump) { + scrollDelta = daRight - screenRight + } + } + } + if (scrollDelta == 0) { + return false + } + doScrollX(if (direction == View.FOCUS_RIGHT) scrollDelta else -scrollDelta) + } + + if (currentFocused != null && currentFocused.isFocused + && isOffScreen(currentFocused)) { + // previously focused item still has focus and is off screen, give + // it up (take it back to ourselves) + // (also, need to temporarily force FOCUS_BEFORE_DESCENDANTS so we are + // sure to + // get it) + val descendantFocusability = descendantFocusability // save + setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS) + requestFocus() + setDescendantFocusability(descendantFocusability) // restore + } + return true + } + + /** + * @return whether the descendant of this scroll view is scrolled off + * * screen. + */ + private fun isOffScreen(descendant: View): Boolean { + return !isWithinDeltaOfScreen(descendant, 0, width) + } + + /** + * @return whether the descendant of this scroll view is within delta + * * pixels of being on the screen. + */ + private fun isWithinDeltaOfScreen(descendant: View, delta: Int, width: Int): Boolean { + descendant.getDrawingRect(mTempRect) + offsetDescendantRectToMyCoords(descendant, mTempRect) + + return mTempRect.right + delta >= scrollX && mTempRect.left - delta <= scrollX + width + } + + /** + * Smooth scroll by a X delta + + * @param delta the number of pixels to scroll by on the X axis + */ + private fun doScrollX(delta: Int) { + if (delta != 0) { + if (isSmoothScrollingEnabled) { + smoothScrollBy(delta, 0) + } else { + scrollBy(delta, 0) + } + } + } + + /** + * Like [View.scrollBy], but scroll smoothly instead of immediately. + + * @param dx the number of pixels to scroll by on the X axis + * * + * @param dy the number of pixels to scroll by on the Y axis + */ + fun smoothScrollBy(dx: Int, dy: Int) { + var dx = dx + if (childCount == 0) { + // Nothing to do. + return + } + val duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll + if (duration > ANIMATED_SCROLL_GAP) { + val width = width - paddingLeft - paddingRight + val right = getChildAt(0).width + val maxX = Math.max(0, right - width) + val scrollX = scrollX + dx = Math.max(0, Math.min(scrollX + dx, maxX)) - scrollX + + mScroller!!.startScroll(scrollX, scrollY, dx, 0) + ViewCompat.postInvalidateOnAnimation(this) + } else { + if (!mScroller!!.isFinished) { + mScroller!!.abortAnimation() + } + scrollBy(dx, dy) + } + mLastScroll = AnimationUtils.currentAnimationTimeMillis() + } + + /** + * Like [.scrollTo], but scroll smoothly instead of immediately. + + * @param x the position where to scroll on the X axis + * * + * @param y the position where to scroll on the Y axis + */ + fun smoothScrollTo(x: Int, y: Int) { + smoothScrollBy(x - scrollX, y - scrollY) + } + + override fun computeHorizontalScrollRange(): Int { + val count = childCount + val contentWidth = width - paddingLeft - paddingRight + if (count == 0) { + return contentWidth + } + + var scrollRange = getChildAt(0).right + val scrollX = scrollX + val overscrollRight = Math.max(0, scrollRange - contentWidth) + if (scrollX < 0) { + scrollRange -= scrollX + } else if (scrollX > overscrollRight) { + scrollRange += scrollX - overscrollRight + } + + return scrollRange + } + + override fun computeHorizontalScrollOffset(): Int { + return Math.max(0, super.computeHorizontalScrollOffset()) + } + + override fun measureChild(child: View, parentWidthMeasureSpec: Int, parentHeightMeasureSpec: Int) { + val lp = child.layoutParams + + val childWidthMeasureSpec: Int + val childHeightMeasureSpec: Int + + childHeightMeasureSpec = ViewGroup.getChildMeasureSpec(parentHeightMeasureSpec, paddingTop + paddingBottom, lp.height) + + childWidthMeasureSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED) + + child.measure(childWidthMeasureSpec, childHeightMeasureSpec) + } + + override fun measureChildWithMargins(child: View, parentWidthMeasureSpec: Int, widthUsed: Int, + parentHeightMeasureSpec: Int, heightUsed: Int) { + val lp = child.layoutParams as ViewGroup.MarginLayoutParams + + val childHeightMeasureSpec = ViewGroup.getChildMeasureSpec(parentHeightMeasureSpec, + paddingTop + paddingBottom + lp.topMargin + lp.bottomMargin + + heightUsed, lp.height) + val childWidthMeasureSpec = View.MeasureSpec.makeMeasureSpec( + lp.leftMargin + lp.rightMargin, View.MeasureSpec.UNSPECIFIED) + + child.measure(childWidthMeasureSpec, childHeightMeasureSpec) + } + + override fun computeScroll() { + if (mScroller!!.computeScrollOffset()) { + val oldX = scrollX + val oldY = scrollY + val x = mScroller!!.currX + val y = mScroller!!.currY + + if (oldX != x || oldY != y) { + val range = scrollRange + val overscrollMode = ViewCompat.getOverScrollMode(this) + val canOverscroll = overscrollMode == ViewCompat.OVER_SCROLL_ALWAYS || overscrollMode == ViewCompat.OVER_SCROLL_IF_CONTENT_SCROLLS && range > 0 + + overScrollByCompat(x - oldX, y - oldY, oldX, oldY, range, 0, + 0, 0, false) + + if (canOverscroll) { + ensureGlows() + if (x <= 0 && oldX > 0) { + mEdgeGlowLeft!!.onAbsorb(mScroller!!.currVelocity.toInt()) + } else if (x >= range && oldX < range) { + mEdgeGlowRight!!.onAbsorb(mScroller!!.currVelocity.toInt()) + } + } + } + } + } + + /** + * Scrolls the view to the given child. + + * @param child the View to scroll to + */ + private fun scrollToChild(child: View) { + child.getDrawingRect(mTempRect) + + /* Offset from child's local coordinates to ScrollView coordinates */ + offsetDescendantRectToMyCoords(child, mTempRect) + + val scrollDelta = computeScrollDeltaToGetChildRectOnScreen(mTempRect) + + if (scrollDelta != 0) { + scrollBy(scrollDelta, 0) + } + } + + /** + * If rect is off screen, scroll just enough to get it (or at least the + * first screen size chunk of it) on screen. + + * @param rect The rectangle. + * * + * @param immediate True to scroll immediately without animation + * * + * @return true if scrolling was performed + */ + private fun scrollToChildRect(rect: Rect, immediate: Boolean): Boolean { + val delta = computeScrollDeltaToGetChildRectOnScreen(rect) + val scroll = delta != 0 + if (scroll) { + if (immediate) { + scrollBy(delta, 0) + } else { + smoothScrollBy(delta, 0) + } + } + return scroll + } + + /** + * Compute the amount to scroll in the Y direction in order to get + * a rectangle completely on the screen (or, if taller than the screen, + * at least the first screen size chunk of it). + + * @param rect The rect. + * * + * @return The scroll delta. + */ + protected fun computeScrollDeltaToGetChildRectOnScreen(rect: Rect): Int { + if (childCount == 0) return 0 + + val width = width + var screenLeft = scrollX + var screenRight = screenLeft + width + + val fadingEdge = horizontalFadingEdgeLength + + // leave room for top fading edge as long as rect isn't at very top + if (rect.left > 0) { + screenLeft += fadingEdge + } + + // leave room for bottom fading edge as long as rect isn't at very bottom + if (rect.right < getChildAt(0).width) { + screenRight -= fadingEdge + } + + var scrollXDelta = 0 + + if (rect.right > screenRight && rect.left > screenLeft) { + // need to move down to get it in view: move down just enough so + // that the entire rectangle is in view (or at least the first + // screen size chunk). + + if (rect.width() > width) { + // just enough to get screen size chunk on + scrollXDelta += rect.left - screenLeft + } else { + // get entire rect at bottom of screen + scrollXDelta += rect.right - screenRight + } + + // make sure we aren't scrolling beyond the end of our content + val right = getChildAt(0).right + val distanceToRight = right - screenRight + scrollXDelta = Math.min(scrollXDelta, distanceToRight) + + } else if (rect.left < screenLeft && rect.right < screenRight) { + // need to move up to get it in view: move up just enough so that + // entire rectangle is in view (or at least the first screen + // size chunk of it). + + if (rect.width() > width) { + // screen size chunk + scrollXDelta -= screenRight - rect.right + } else { + // entire rect at top + scrollXDelta -= screenLeft - rect.left + } + + // make sure we aren't scrolling any further than the top our content + scrollXDelta = Math.max(scrollXDelta, -scrollX) + } + return scrollXDelta + } + + override fun requestChildFocus(child: View, focused: View) { + if (!mIsLayoutDirty) { + scrollToChild(focused) + } else { + // The child may not be laid out yet, we can't compute the scroll yet + mChildToScrollTo = focused + } + super.requestChildFocus(child, focused) + } + + + /** + * When looking for focus in children of a scroll view, need to be a little + * more careful not to give focus to something that is scrolled off screen. + + * This is more expensive than the default [ViewGroup] + * implementation, otherwise this behavior might have been made the default. + */ + override fun onRequestFocusInDescendants(direction: Int, + previouslyFocusedRect: Rect?): Boolean { + var direction = direction + + // convert from forward / backward notation to up / down / left / right + // (ugh). + if (direction == View.FOCUS_FORWARD) { + direction = View.FOCUS_RIGHT + } else if (direction == View.FOCUS_BACKWARD) { + direction = View.FOCUS_LEFT + } + + val nextFocus = (if (previouslyFocusedRect == null) + FocusFinder.getInstance().findNextFocus(this, null, direction) + else + FocusFinder.getInstance().findNextFocusFromRect(this, + previouslyFocusedRect, direction)) ?: return false + + if (isOffScreen(nextFocus)) { + return false + } + + return nextFocus.requestFocus(direction, previouslyFocusedRect) + } + + override fun requestChildRectangleOnScreen(child: View, rectangle: Rect, + immediate: Boolean): Boolean { + // offset into coordinate space of this scroll view + rectangle.offset(child.left - child.scrollX, + child.top - child.scrollY) + + return scrollToChildRect(rectangle, immediate) + } + + @SuppressLint("MissingSuperCall") + override fun requestLayout() { + mIsLayoutDirty = true + super.requestLayout() + } + + override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) { + super.onLayout(changed, l, t, r, b) + mIsLayoutDirty = false + // Give a child focus if it needs it + if (mChildToScrollTo != null && isViewDescendantOf(mChildToScrollTo!!, this)) { + scrollToChild(mChildToScrollTo!!) + } + mChildToScrollTo = null + + if (!mIsLaidOut) { + if (mSavedState != null) { + scrollTo(mSavedState!!.scrollPosition, scrollY) + mSavedState = null + } // mScrollY default value is "0" + + val childWidth = if (childCount > 0) getChildAt(0).measuredWidth else 0 + val scrollRange = Math.max(0, + childWidth - (r - l - paddingRight - paddingLeft)) + + // Don't forget to clamp + if (scrollX > scrollRange) { + scrollTo(scrollRange, scrollY) + } else if (scrollX < 0) { + scrollTo(0, scrollY) + } + } + + // Calling this with the present values causes it to re-claim them + scrollTo(scrollX, scrollY) + mIsLaidOut = true + } + + @SuppressLint("MissingSuperCall") + public override fun onAttachedToWindow() { + mIsLaidOut = false + } + + override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) { + super.onSizeChanged(w, h, oldw, oldh) + + val currentFocused = findFocus() + if (null == currentFocused || this === currentFocused) + return + + // If the currently-focused view was visible on the screen when the + // screen was at the old height, then scroll the screen to make that + // view visible with the new screen height. + if (isWithinDeltaOfScreen(currentFocused, 0, oldw)) { + currentFocused.getDrawingRect(mTempRect) + offsetDescendantRectToMyCoords(currentFocused, mTempRect) + val scrollDelta = computeScrollDeltaToGetChildRectOnScreen(mTempRect) + doScrollX(scrollDelta) + } + } + + /** + * Fling the scroll view + + * @param velocityX The initial velocity in the X direction. Positive + * * numbers mean that the finger/cursor is moving down the screen, + * * which means we want to scroll towards the top. + */ + fun fling(velocityX: Int) { + if (childCount > 0) { + val width = width - paddingRight - paddingLeft + val right = getChildAt(0).width + + mScroller!!.fling(scrollX, scrollY, velocityX, 0, 0, Math.max(0, right - width), 0, + 0, width / 2, 0) + + ViewCompat.postInvalidateOnAnimation(this) + } + } + + private fun flingWithNestedDispatch(velocityX: Int) { + val scrollX = scrollX + val canFling = (scrollX > 0 || velocityX > 0) && (scrollX < scrollRange || velocityX < 0) + if (!dispatchNestedPreFling(velocityX.toFloat(), 0f)) { + dispatchNestedFling(velocityX.toFloat(), 0f, canFling) + if (canFling) { + fling(velocityX) + } + } + } + + private fun endDrag() { + mIsBeingDragged = false + + recycleVelocityTracker() + stopNestedScroll() + + if (mEdgeGlowLeft != null) { + mEdgeGlowLeft!!.onRelease() + mEdgeGlowRight!!.onRelease() + } + } + + /** + * {@inheritDoc} + + * + * This version also clamps the scrolling to the bounds of our child. + */ + override fun scrollTo(x: Int, y: Int) { + var x = x + var y = y + // we rely on the fact the View.scrollBy calls scrollTo. + if (childCount > 0) { + val child = getChildAt(0) + x = clamp(x, width - paddingRight - paddingLeft, child.width) + y = clamp(y, height - paddingBottom - paddingTop, child.height) + if (x != scrollX || y != scrollY) { + super.scrollTo(x, y) + } + } + } + + private fun ensureGlows() { + if (ViewCompat.getOverScrollMode(this) != ViewCompat.OVER_SCROLL_NEVER) { + if (mEdgeGlowLeft == null) { + val context = context + mEdgeGlowLeft = EdgeEffectCompat(context) + mEdgeGlowRight = EdgeEffectCompat(context) + + } + } else { + mEdgeGlowLeft = null + mEdgeGlowRight = null + } + } + + override fun draw(canvas: Canvas) { + super.draw(canvas) + if (mEdgeGlowLeft != null) { + val scrollX = scrollX + if (!mEdgeGlowLeft!!.isFinished) { + val restoreCount = canvas.save() + val height = height - paddingTop - paddingBottom + + canvas.translate(Math.min(0, scrollX).toFloat(), paddingTop.toFloat()) + canvas.rotate(90f, 0f, height.toFloat()) + mEdgeGlowLeft!!.setSize(width, height) + if (mEdgeGlowLeft!!.draw(canvas)) { + ViewCompat.postInvalidateOnAnimation(this) + } + canvas.restoreToCount(restoreCount) + } + if (!mEdgeGlowRight!!.isFinished) { + val restoreCount = canvas.save() + val width = width + val height = height - paddingTop - paddingBottom + + canvas.translate((Math.max(scrollRange, scrollX) + width).toFloat(), + (-height + paddingTop).toFloat()) + canvas.rotate(90f, 0f, height.toFloat()) + mEdgeGlowRight!!.setSize(width, height) + if (mEdgeGlowRight!!.draw(canvas)) { + ViewCompat.postInvalidateOnAnimation(this) + } + canvas.restoreToCount(restoreCount) + } + } + } + + override fun onRestoreInstanceState(state: Parcelable) { + val ss = state as SavedState + super.onRestoreInstanceState(ss.superState) + mSavedState = ss + requestLayout() + } + + override fun onSaveInstanceState(): Parcelable? { + val superState = super.onSaveInstanceState() + val ss = SavedState(superState) + ss.scrollPosition = scrollX + return ss + } + + internal class SavedState : View.BaseSavedState { + var scrollPosition: Int = 0 + + constructor(superState: Parcelable) : super(superState) {} + + constructor(source: Parcel) : super(source) { + scrollPosition = source.readInt() + } + + override fun writeToParcel(dest: Parcel, flags: Int) { + super.writeToParcel(dest, flags) + dest.writeInt(scrollPosition) + } + + override fun toString(): String { + return "HorizontalScrollView.SavedState{${Integer.toHexString(System.identityHashCode(this))} scrollPosition=${scrollPosition}}" + } + + companion object { + + val CREATOR: Parcelable.Creator = object : Parcelable.Creator { + override fun createFromParcel(`in`: Parcel): SavedState { + return SavedState(`in`) + } + + override fun newArray(size: Int): Array { + return arrayOfNulls(size) + } + } + } + } + + internal class AccessibilityDelegate : AccessibilityDelegateCompat() { + override fun performAccessibilityAction(host: View, action: Int, arguments: Bundle): Boolean { + if (super.performAccessibilityAction(host, action, arguments)) { + return true + } + val nsvHost = host as NestHorizontalScrollView + if (!nsvHost.isEnabled) { + return false + } + when (action) { + AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD -> { + run { + val viewportWidth = nsvHost.width - nsvHost.paddingRight + -nsvHost.paddingLeft + val targetScrollX = Math.min(nsvHost.scrollX + viewportWidth, + nsvHost.scrollRange) + if (targetScrollX != nsvHost.scrollX) { + nsvHost.smoothScrollTo(targetScrollX, 0) + return true + } + } + return false + } + AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD -> { + run { + val viewportWidth = nsvHost.width - nsvHost.paddingRight + -nsvHost.paddingLeft + val targetScrollX = Math.max(nsvHost.scrollX - viewportWidth, 0) + if (targetScrollX != nsvHost.scrollX) { + nsvHost.smoothScrollTo(0, targetScrollX) + return true + } + } + return false + } + } + return false + } + + override fun onInitializeAccessibilityNodeInfo(host: View, info: AccessibilityNodeInfoCompat) { + super.onInitializeAccessibilityNodeInfo(host, info) + val nsvHost = host as NestHorizontalScrollView + info.className = ScrollView::class.java.name + if (nsvHost.isEnabled) { + val scrollRange = nsvHost.scrollRange + if (scrollRange > 0) { + info.isScrollable = true + if (nsvHost.scrollX > 0) { + info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_BACKWARD) + } + if (nsvHost.scrollX < scrollRange) { + info.addAction(AccessibilityNodeInfoCompat.ACTION_SCROLL_FORWARD) + } + } + } + } + + override fun onInitializeAccessibilityEvent(host: View, event: AccessibilityEvent) { + super.onInitializeAccessibilityEvent(host, event) + val nsvHost = host as NestHorizontalScrollView + event.className = ScrollView::class.java.name + val record = AccessibilityEventCompat.asRecord(event) + val scrollable = nsvHost.scrollRange > 0 + record.isScrollable = scrollable + record.scrollX = nsvHost.scrollX + record.scrollY = nsvHost.scrollY + record.maxScrollX = nsvHost.scrollRange + record.maxScrollY = nsvHost.scrollY + } + } + + companion object { + internal val ANIMATED_SCROLL_GAP = 250 + + internal val MAX_SCROLL_FACTOR = 0.5f + + private val TAG = "NestedScrollView" + + /** + * Sentinel value for no current active pointer. + * Used by [.mActivePointerId]. + */ + private val INVALID_POINTER = -1 + + private val ACCESSIBILITY_DELEGATE = AccessibilityDelegate() + + private val SCROLLVIEW_STYLEABLE = intArrayOf(android.R.attr.fillViewport) + + /** + * Return true if child is a descendant of parent, (or equal to the parent). + */ + private fun isViewDescendantOf(child: View, parent: View): Boolean { + if (child === parent) { + return true + } + + val theParent = child.parent + return theParent is ViewGroup && isViewDescendantOf(theParent as View, parent) + } + + private fun clamp(n: Int, my: Int, child: Int): Int { + if (my >= child || n < 0) { + /* my >= child is this case: + * |--------------- me ---------------| + * |------ child ------| + * or + * |--------------- me ---------------| + * |------ child ------| + * or + * |--------------- me ---------------| + * |------ child ------| + * + * n < 0 is this case: + * |------ me ------| + * |-------- child --------| + * |-- mScrollX --| + */ + return 0 + } + if (my + n > child) { + /* this case: + * |------ me ------| + * |------ child ------| + * |-- mScrollX --| + */ + return child - my + } + return n + } + } +} diff --git a/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/view/NestedScrollWebView.kt b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/view/NestedScrollWebView.kt new file mode 100644 index 0000000..12e3ed3 --- /dev/null +++ b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/view/NestedScrollWebView.kt @@ -0,0 +1,156 @@ +package com.loopeer.codereaderkt.ui.view + +import android.content.Context +import android.support.v4.view.MotionEventCompat +import android.support.v4.view.NestedScrollingChild +import android.support.v4.view.NestedScrollingChildHelper +import android.support.v4.view.ViewCompat +import android.util.AttributeSet +import android.view.MotionEvent +import android.webkit.WebView + +/* +* https://github.com/rhlff/NestedScrollWebView +* */ +class NestedScrollWebView : WebView, NestedScrollingChild { + + interface ScrollChangeListener { + fun onScrollChanged(l: Int, t: Int, oldl: Int, oldt: Int) + } + + private var mLastMotionY: Int = 0 + + private val mScrollOffset = IntArray(2) + private val mScrollConsumed = IntArray(2) + + private var mNestedYOffset: Int = 0 + + private var mChildHelper: NestedScrollingChildHelper? = null + + private var mScrollChangeListener: ScrollChangeListener? = null + + constructor(context: Context) : super(context) { + init() + } + + constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { + init() + } + + constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { + init() + } + + private fun init() { + mChildHelper = NestedScrollingChildHelper(this) + isNestedScrollingEnabled = true + } + + override fun onTouchEvent(event: MotionEvent): Boolean { + var result = false + + val trackedEvent = MotionEvent.obtain(event) + + val action = MotionEventCompat.getActionMasked(event) + + if (action == MotionEvent.ACTION_DOWN) { + mNestedYOffset = 0 + } + + val y = event.y.toInt() + + event.offsetLocation(0f, mNestedYOffset.toFloat()) + + when (action) { + MotionEvent.ACTION_DOWN -> { + mLastMotionY = y + startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL) + result = super.onTouchEvent(event) + } + MotionEvent.ACTION_MOVE -> { + var deltaY = mLastMotionY - y + + if (dispatchNestedPreScroll(0, deltaY, mScrollConsumed, mScrollOffset)) { + deltaY -= mScrollConsumed[1] + trackedEvent.offsetLocation(0f, mScrollOffset[1].toFloat()) + mNestedYOffset += mScrollOffset[1] + } + + val oldY = scrollY + mLastMotionY = y - mScrollOffset[1] + if (deltaY < 0) { + val newScrollY = Math.max(0, oldY + deltaY) + deltaY -= newScrollY - oldY + if (dispatchNestedScroll(0, newScrollY - deltaY, 0, deltaY, mScrollOffset)) { + mLastMotionY -= mScrollOffset[1] + trackedEvent.offsetLocation(0f, mScrollOffset[1].toFloat()) + mNestedYOffset += mScrollOffset[1] + } + } + + trackedEvent.recycle() + result = super.onTouchEvent(trackedEvent) + } + MotionEvent.ACTION_POINTER_DOWN, MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> { + stopNestedScroll() + result = super.onTouchEvent(event) + } + } + return result + } + + // NestedScrollingChild + + override fun setNestedScrollingEnabled(enabled: Boolean) { + mChildHelper!!.isNestedScrollingEnabled = enabled + } + + override fun isNestedScrollingEnabled(): Boolean { + return mChildHelper!!.isNestedScrollingEnabled + } + + override fun startNestedScroll(axes: Int): Boolean { + return mChildHelper!!.startNestedScroll(axes) + } + + override fun stopNestedScroll() { + mChildHelper!!.stopNestedScroll() + } + + override fun hasNestedScrollingParent(): Boolean { + return mChildHelper!!.hasNestedScrollingParent() + } + + override fun dispatchNestedScroll(dxConsumed: Int, dyConsumed: Int, dxUnconsumed: Int, dyUnconsumed: Int, offsetInWindow: IntArray?): Boolean { + return mChildHelper!!.dispatchNestedScroll(dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed, offsetInWindow) + } + + override fun dispatchNestedPreScroll(dx: Int, dy: Int, consumed: IntArray?, offsetInWindow: IntArray?): Boolean { + return mChildHelper!!.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow) + } + + override fun dispatchNestedFling(velocityX: Float, velocityY: Float, consumed: Boolean): Boolean { + return mChildHelper!!.dispatchNestedFling(velocityX, velocityY, consumed) + } + + override fun dispatchNestedPreFling(velocityX: Float, velocityY: Float): Boolean { + return mChildHelper!!.dispatchNestedPreFling(velocityX, velocityY) + } + + fun setScrollChangeListener(scrollChangeListener: ScrollChangeListener) { + mScrollChangeListener = scrollChangeListener + } + + override fun onScrollChanged(l: Int, t: Int, oldl: Int, oldt: Int) { + super.onScrollChanged(l, t, oldl, oldt) + if (mScrollChangeListener != null) { + mScrollChangeListener!!.onScrollChanged(l, t, oldl, oldt) + } + } + + companion object { + + val TAG = NestedScrollWebView::class.java.simpleName + } + +} \ No newline at end of file diff --git a/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/view/ProgressIndicatorView.kt b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/view/ProgressIndicatorView.kt new file mode 100644 index 0000000..e914b7a --- /dev/null +++ b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/view/ProgressIndicatorView.kt @@ -0,0 +1,179 @@ +package com.loopeer.codereaderkt.ui.view + +import android.animation.Animator +import android.animation.ValueAnimator +import android.content.Context +import android.graphics.Canvas +import android.graphics.Paint +import android.graphics.RectF +import android.support.v4.content.ContextCompat +import android.util.AttributeSet +import android.view.View + +import com.loopeer.codereaderkt.R + +import java.util.ArrayList + +class ProgressIndicatorView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) : View(context, attrs, defStyleAttr) { + + internal var scaleFloat1: Float = 0.toFloat() + internal var scaleFloat2: Float = 0.toFloat() + internal var degrees: Float = 0.toFloat() + + internal var mIndicatorColor: Int = 0 + internal var mPaint: Paint?=null + private var mAnimators: List? = null + private var mHasAnimation: Boolean = false + + init { + init(context, attrs, defStyleAttr) + } + + private fun init(context: Context, attrs: AttributeSet?, defStyleAttr: Int) { + mIndicatorColor = ContextCompat.getColor(getContext(), R.color.colorPrimary) + mPaint = Paint() + mPaint?.color = mIndicatorColor + mPaint?.style = Paint.Style.FILL + mPaint?.isAntiAlias = true + } + + override fun onAttachedToWindow() { + super.onAttachedToWindow() + setAnimationStatus(AnimStatus.START) + } + + override fun onDetachedFromWindow() { + super.onDetachedFromWindow() + setAnimationStatus(AnimStatus.CANCEL) + } + + override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec) + val width = measureDimension(dp2px(DEFAULT_SIZE), widthMeasureSpec) + val height = measureDimension(dp2px(DEFAULT_SIZE), heightMeasureSpec) + setMeasuredDimension(width, height) + } + + private fun dp2px(dpValue: Int): Int { + return context.resources.displayMetrics.density.toInt() * dpValue + } + + private fun measureDimension(defaultSize: Int, measureSpec: Int): Int { + var result = defaultSize + val specMode = View.MeasureSpec.getMode(measureSpec) + val specSize = View.MeasureSpec.getSize(measureSpec) + if (specMode == View.MeasureSpec.EXACTLY) { + result = specSize + } else if (specMode == View.MeasureSpec.AT_MOST) { + result = Math.min(defaultSize, specSize) + } else { + result = defaultSize + } + return result + } + + override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { + super.onLayout(changed, left, top, right, bottom) + if (!mHasAnimation) { + mHasAnimation = true + initAnimation() + } + } + + override fun onDraw(canvas: Canvas) { + super.onDraw(canvas) + val circleSpacing = 12f + val x = (width / 2).toFloat() + val y = (height / 2).toFloat() + + canvas.save() + canvas.translate(x, y) + canvas.scale(scaleFloat1, scaleFloat1) + mPaint?.style = Paint.Style.FILL + canvas.drawCircle(0f, 0f, x / 2.5f, mPaint) + + canvas.restore() + + canvas.translate(x, y) + canvas.scale(scaleFloat2, scaleFloat2) + canvas.rotate(degrees) + + mPaint?.strokeWidth = 3f + mPaint?.style = Paint.Style.STROKE + + val startAngles = floatArrayOf(225f, 45f) + for (i in 0..1) { + val rectF = RectF(-x + circleSpacing, -y + circleSpacing, x - circleSpacing, y - circleSpacing) + canvas.drawArc(rectF, startAngles[i], 90f, false, mPaint) + } + } + + fun initAnimation() { + mAnimators = createAnimation() + } + + fun setAnimationStatus(animStatus: AnimStatus) { + if (mAnimators == null) { + return + } + val count = mAnimators!!.size + for (i in 0..count - 1) { + val animator = mAnimators!![i] + val isRunning = animator.isRunning + when (animStatus) { + ProgressIndicatorView.AnimStatus.START -> if (!isRunning) { + animator.start() + } + ProgressIndicatorView.AnimStatus.END -> if (isRunning) { + animator.end() + } + ProgressIndicatorView.AnimStatus.CANCEL -> if (isRunning) { + animator.cancel() + } + } + } + } + + enum class AnimStatus { + START, END, CANCEL + } + + fun createAnimation(): List { + val scaleAnim = ValueAnimator.ofFloat(1f, 0.3f, 1f) + scaleAnim.duration = 1000 + scaleAnim.repeatCount = -1 + scaleAnim.addUpdateListener { animation -> + scaleFloat1 = animation.animatedValue as Float + postInvalidate() + } + scaleAnim.start() + + val scaleAnim2 = ValueAnimator.ofFloat(1f, 0.6f, 1f) + scaleAnim2.duration = 1000 + scaleAnim2.repeatCount = -1 + scaleAnim2.addUpdateListener { animation -> + scaleFloat2 = animation.animatedValue as Float + postInvalidate() + } + scaleAnim2.start() + + val rotateAnim = ValueAnimator.ofFloat(0f, 180f, 360f) + rotateAnim.duration = 1000 + rotateAnim.repeatCount = -1 + rotateAnim.addUpdateListener { animation -> + degrees = animation.animatedValue as Float + postInvalidate() + } + rotateAnim.start() + + val animators = ArrayList() + animators.add(scaleAnim) + animators.add(scaleAnim2) + animators.add(rotateAnim) + return animators + } + + companion object { + val DEFAULT_SIZE = 45 + } +} diff --git a/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/view/ProgressLoading.kt b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/view/ProgressLoading.kt new file mode 100644 index 0000000..c5dea27 --- /dev/null +++ b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/view/ProgressLoading.kt @@ -0,0 +1,77 @@ +package com.loopeer.codereaderkt.ui.view + +import android.app.Dialog +import android.content.Context +import android.graphics.drawable.ColorDrawable +import android.text.TextUtils +import android.view.View +import android.view.Window +import android.widget.TextView + +import com.loopeer.codereaderkt.R + +open class ProgressLoading(context: Context, theme: Int) : Dialog(context, theme) { + + private lateinit var mProgressView: View + private lateinit var mMessageTextView: TextView + + private lateinit var mWindow: Window + + private lateinit var mMessage: CharSequence + private var mShowProgress = true + + init { + initialize(context, theme) + } + + private fun initialize(context: Context, theme: Int) { + mWindow = window + mWindow.requestFeature(Window.FEATURE_NO_TITLE) + mWindow.setBackgroundDrawable(ColorDrawable(android.graphics.Color.TRANSPARENT)) + setContentView(R.layout.progress_loading) + mMessageTextView = findViewById(R.id.text_message) + mProgressView = findViewById(R.id.progress) + } + + fun setMessage(msg: CharSequence): ProgressLoading { + mMessage = msg + return this + } + + private fun updateMessage(msg: CharSequence): ProgressLoading { + mMessage = msg + show() + return this + } + + fun hideMessage(): ProgressLoading { + return updateMessage("") + } + + fun hideProgressBar(): ProgressLoading { + mShowProgress = false + show() + return this + } + + override fun show() { + if (mMessageTextView != null) { + if (TextUtils.isEmpty(mMessage)) { + mMessageTextView.visibility = View.GONE + } else { + mMessageTextView.visibility = View.VISIBLE + mMessageTextView.text = mMessage + } + } + + if (mProgressView != null) { + if (mShowProgress) { + mProgressView.visibility = View.VISIBLE + } else { + mProgressView.visibility = View.GONE + } + } + super.show() + } + +} \ No newline at end of file diff --git a/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/view/ProgressWebView.kt b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/view/ProgressWebView.kt new file mode 100644 index 0000000..a54ab0c --- /dev/null +++ b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/view/ProgressWebView.kt @@ -0,0 +1,52 @@ +package com.loopeer.codereaderkt.ui.view + +import android.content.Context +import android.support.v4.content.ContextCompat +import android.util.AttributeSet +import android.view.View +import android.webkit.WebView +import android.widget.AbsoluteLayout +import android.widget.ProgressBar + +import com.loopeer.codereaderkt.R + +class ProgressWebView(context: Context, attrs: AttributeSet) : WebView(context, attrs) { + + private val mProgressBar: ProgressBar + + init { + mProgressBar = ProgressBar(context, null, android.R.attr.progressBarStyleHorizontal) + mProgressBar.isIndeterminate = false + mProgressBar.progressDrawable = ContextCompat.getDrawable(context, R.drawable.progress_horizontal_web_view) + mProgressBar.indeterminateDrawable = ContextCompat.getDrawable(context, android.R.drawable.progress_indeterminate_horizontal) + mProgressBar.layoutParams = AbsoluteLayout.LayoutParams(AbsoluteLayout.LayoutParams.MATCH_PARENT, 5, 0, 0) + mProgressBar.minimumHeight = 16 + addView(mProgressBar) + webChromeClient = WebChromeClient() + } + + fun setProgressbarGone() { + mProgressBar.visibility = View.GONE + } + + inner class WebChromeClient : android.webkit.WebChromeClient() { + override fun onProgressChanged(view: WebView, newProgress: Int) { + if (newProgress == 100) { + mProgressBar.visibility = View.GONE + } else { + if (mProgressBar.visibility == View.GONE) mProgressBar.visibility = View.VISIBLE + mProgressBar.progress = newProgress + } + super.onProgressChanged(view, newProgress) + } + + } + + override fun onScrollChanged(l: Int, t: Int, oldl: Int, oldt: Int) { + val lp = mProgressBar.layoutParams as AbsoluteLayout.LayoutParams + lp.x = l + lp.y = t + mProgressBar.layoutParams = lp + super.onScrollChanged(l, t, oldl, oldt) + } +} \ No newline at end of file diff --git a/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/view/ScrollAwareFABBehavior.kt b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/view/ScrollAwareFABBehavior.kt new file mode 100644 index 0000000..b916094 --- /dev/null +++ b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/view/ScrollAwareFABBehavior.kt @@ -0,0 +1,30 @@ +package com.loopeer.codereaderkt.ui.view + +import android.content.Context +import android.support.design.widget.CoordinatorLayout +import android.support.design.widget.FloatingActionButton +import android.support.v4.view.ViewCompat +import android.util.AttributeSet +import android.view.View + +class ScrollAwareFABBehavior(context: Context, attrs: AttributeSet) : FloatingActionButton.Behavior() { + + override fun onStartNestedScroll(coordinatorLayout: CoordinatorLayout?, child: FloatingActionButton?, + directTargetChild: View?, target: View?, nestedScrollAxes: Int): Boolean { + // Ensure we react to vertical scrolling + return nestedScrollAxes == ViewCompat.SCROLL_AXIS_VERTICAL || super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, nestedScrollAxes) + } + + override fun onNestedScroll(coordinatorLayout: CoordinatorLayout?, child: FloatingActionButton?, + target: View?, dxConsumed: Int, dyConsumed: Int, + dxUnconsumed: Int, dyUnconsumed: Int) { + super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, dyConsumed, dxUnconsumed, dyUnconsumed) + if (dyConsumed > 0 && child!!.visibility == View.VISIBLE) { + // User scrolled down and the FAB is currently visible -> hide the FAB + child.hide() + } else if (dyConsumed < 0 && child!!.visibility != View.VISIBLE) { + // User scrolled up and the FAB is currently not visible -> show the FAB + child.show() + } + } +} \ No newline at end of file diff --git a/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/view/TextWatcherImpl.kt b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/view/TextWatcherImpl.kt new file mode 100644 index 0000000..eb42aea --- /dev/null +++ b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/view/TextWatcherImpl.kt @@ -0,0 +1,20 @@ +package com.loopeer.codereaderkt.ui.view + +import android.text.Editable +import android.text.TextWatcher + + +open class TextWatcherImpl : TextWatcher { + + override fun beforeTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) { + + } + + override fun onTextChanged(charSequence: CharSequence, i: Int, i1: Int, i2: Int) { + + } + + override fun afterTextChanged(editable: Editable) { + + } +} \ No newline at end of file diff --git a/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/view/ThemeChooser.kt b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/view/ThemeChooser.kt new file mode 100644 index 0000000..0aaa033 --- /dev/null +++ b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/view/ThemeChooser.kt @@ -0,0 +1,39 @@ +package com.loopeer.codereaderkt.ui.view + +import android.app.Activity +import android.content.Context +import android.view.View +import android.widget.ImageView +import java.util.HashMap + + +class ThemeChooser(private val mContext: Context, private val mOnItemSelectListener: OnItemSelectListener) { + interface OnItemSelectListener { + fun onItemSelect(id: Int, tag: String) + } + + private val mViewThemeTags: HashMap + + init { + mViewThemeTags = HashMap() + } + + fun addItem(id: Int, tag: String) { + mViewThemeTags.put(id, tag) + } + + fun onItemSelect(view: View) { + view.isSelected = true + mOnItemSelectListener.onItemSelect(view.id, mViewThemeTags[view.id] as String) + mViewThemeTags.keys + .filter { view.id != it } + .forEach { (mContext as Activity).findViewById(it).isSelected = false } + } + + fun onItemSelectByTag(tag: String) { + for ((id, value) in mViewThemeTags) { + (mContext as Activity).findViewById(id).isSelected = value == tag + } + } + +} diff --git a/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/viewHolder/DatabindingViewHolder.kt b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/viewHolder/DatabindingViewHolder.kt new file mode 100644 index 0000000..84ae1a8 --- /dev/null +++ b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/ui/viewHolder/DatabindingViewHolder.kt @@ -0,0 +1,13 @@ +package com.loopeer.codereaderkt.ui.viewHolder + +import android.databinding.DataBindingUtil +import android.databinding.ViewDataBinding +import android.support.v7.widget.RecyclerView +import android.view.View + + +open class DataBindingViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { + + val binding: T = DataBindingUtil.bind(itemView) + +} diff --git a/code-reader-kt/src/main/java/com/loopeer/codereaderkt/utils/Base64.kt b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/utils/Base64.kt new file mode 100644 index 0000000..fb2deb7 --- /dev/null +++ b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/utils/Base64.kt @@ -0,0 +1,228 @@ +package com.loopeer.codereaderkt.utils + +import java.io.UnsupportedEncodingException + +object Base64 { + + /** The equals sign (=) as a byte. */ + private val EQUALS_SIGN = '='.toByte() + + /** Preferred encoding. */ + private val PREFERRED_ENCODING = "US-ASCII" + + /** The 64 valid Base64 values. */ + private val _STANDARD_ALPHABET = byteArrayOf('A'.toByte(), 'B'.toByte(), 'C'.toByte(), 'D'.toByte(), 'E'.toByte(), 'F'.toByte(), 'G'.toByte(), 'H'.toByte(), 'I'.toByte(), 'J'.toByte(), 'K'.toByte(), 'L'.toByte(), 'M'.toByte(), 'N'.toByte(), 'O'.toByte(), 'P'.toByte(), 'Q'.toByte(), 'R'.toByte(), 'S'.toByte(), 'T'.toByte(), 'U'.toByte(), 'V'.toByte(), 'W'.toByte(), 'X'.toByte(), 'Y'.toByte(), 'Z'.toByte(), 'a'.toByte(), 'b'.toByte(), 'c'.toByte(), 'd'.toByte(), 'e'.toByte(), 'f'.toByte(), 'g'.toByte(), 'h'.toByte(), 'i'.toByte(), 'j'.toByte(), 'k'.toByte(), 'l'.toByte(), 'm'.toByte(), 'n'.toByte(), 'o'.toByte(), 'p'.toByte(), 'q'.toByte(), 'r'.toByte(), 's'.toByte(), 't'.toByte(), 'u'.toByte(), 'v'.toByte(), 'w'.toByte(), 'x'.toByte(), 'y'.toByte(), 'z'.toByte(), '0'.toByte(), '1'.toByte(), '2'.toByte(), '3'.toByte(), '4'.toByte(), '5'.toByte(), '6'.toByte(), '7'.toByte(), '8'.toByte(), '9'.toByte(), '+'.toByte(), '/'.toByte()) + + /** + * + * + * Encodes up to three bytes of the array source and writes the + * resulting four Base64 bytes to destination. The source and + * destination arrays can be manipulated anywhere along their length by + * specifying srcOffset and destOffset. This method + * does not check to make sure your arrays are large enough to accomodate + * srcOffset + 3 for the source array or + * destOffset + 4 for the destination array. The + * actual number of significant bytes in your array is given by + * numSigBytes. + * + * + * + * This is the lowest level of the encoding methods with all possible + * parameters. + * + + * @param source + * * the array to convert + * * + * @param srcOffset + * * the index where conversion begins + * * + * @param numSigBytes + * * the number of significant bytes in your array + * * + * @param destination + * * the array to hold the conversion + * * + * @param destOffset + * * the index where output will be put + * * + * @return the destination array + * * + * @since 1.3 + */ + private fun encode3to4(source: ByteArray, srcOffset: Int, + numSigBytes: Int, destination: ByteArray, destOffset: Int): ByteArray { + + val ALPHABET = _STANDARD_ALPHABET + val inBuff = (if (numSigBytes > 0) ((source[srcOffset] as Int).shl(24)).ushr(8) else 0) or (if (numSigBytes > 1) ((source[srcOffset + 1] as Int).shl(24)).ushr(16) else 0) or if (numSigBytes > 2) ((source[srcOffset + 2] as Int).shl(24)).ushr(24) else 0 + when (numSigBytes) { + 3 -> { + destination[destOffset] = ALPHABET[inBuff.ushr(18)] + destination[destOffset + 1] = ALPHABET[inBuff.ushr(12) and 0x3f] + destination[destOffset + 2] = ALPHABET[inBuff.ushr(6) and 0x3f] + destination[destOffset + 3] = ALPHABET[inBuff and 0x3f] + return destination + } + + 2 -> { + destination[destOffset] = ALPHABET[inBuff.ushr(18)] + destination[destOffset + 1] = ALPHABET[inBuff.ushr(12) and 0x3f] + destination[destOffset + 2] = ALPHABET[inBuff.ushr(6) and 0x3f] + destination[destOffset + 3] = EQUALS_SIGN + return destination + } + + 1 -> { + destination[destOffset] = ALPHABET[inBuff.ushr(18)] + destination[destOffset + 1] = ALPHABET[inBuff.ushr(12) and 0x3f] + destination[destOffset + 2] = EQUALS_SIGN + destination[destOffset + 3] = EQUALS_SIGN + return destination + } + + else -> return destination + } + } + + /** + * Encode string as a byte array in Base64 annotation. + + * @param string + * * + * @return The Base64-encoded data as a string + */ + fun encode(string: String): String { + var bytes: ByteArray + try { + bytes = string.toByteArray(charset(PREFERRED_ENCODING)) + } catch (e: UnsupportedEncodingException) { + bytes = string.toByteArray() + } + + return encodeBytes(bytes) + } + + /** + * Encodes a byte array into Base64 notation. + + * @param source + * * The data to convert + * * + * @param off + * * Offset in array where conversion should begin + * * + * @param len + * * Length of data to convert + * * + * @return The Base64-encoded data as a String + * * + * @throws NullPointerException + * * if source array is null + * * + * @throws IllegalArgumentException + * * if source array, offset, or length are invalid + * * + * @since 2.0 + */ + @JvmOverloads fun encodeBytes(source: ByteArray, off: Int = 0, len: Int = source.size): String { + val encoded = encodeBytesToBytes(source, off, len) + try { + return String(encoded, charset(PREFERRED_ENCODING)) + } catch (uue: UnsupportedEncodingException) { + return String(encoded) + } + + } + + + + + /** + * Similar to [.encodeBytes] but returns a byte + * array instead of instantiating a String. This is more efficient if you're + * working with I/O streams and have large data sets to encode. + + + * @param source + * * The data to convert + * * + * @param off + * * Offset in array where conversion should begin + * * + * @param len + * * Length of data to convert + * * + * @return The Base64-encoded data as a String if there is an error + * * + * @throws NullPointerException + * * if source array is null + * * + * @throws IllegalArgumentException + * * if source array, offset, or length are invalid + * * + * @since 2.3.1 + */ + fun encodeBytesToBytes(source: ByteArray?, off: Int, len: Int): ByteArray { + + if (source == null) + throw NullPointerException("Cannot serialize a null array.") + + if (off < 0) + throw IllegalArgumentException("Cannot have negative offset: " + off) + + if (len < 0) + throw IllegalArgumentException("Cannot have length offset: " + len) + + if (off + len > source.size) + throw IllegalArgumentException( + String + .format( + "Cannot have offset of %d and length of %d with array of length %d", + off, len, source.size)) + + // Bytes needed for actual encoding + val encLen = len / 3 * 4 + if (len % 3 > 0) 4 else 0 + + val outBuff = ByteArray(encLen) + + var d = 0 + var e = 0 + val len2 = len - 2 + while (d < len2) { + encode3to4(source, d + off, 3, outBuff, e) + d += 3 + e += 4 + } + + if (d < len) { + encode3to4(source, d + off, len - d, outBuff, e) + e += 4 + } + + if (e <= outBuff.size - 1) { + val finalOut = ByteArray(e) + System.arraycopy(outBuff, 0, finalOut, 0, e) + return finalOut + } else + return outBuff + } +} +/** Defeats instantiation. */ +/** + * Encodes a byte array into Base64 notation. + + * @param source + * * The data to convert + * * + * @return The Base64-encoded data as a String + * * + * @throws NullPointerException + * * if source array is null + * * + * @throws IllegalArgumentException + * * if source array, offset, or length are invalid + * * + * @since 2.0 + */ + diff --git a/code-reader-kt/src/main/java/com/loopeer/codereaderkt/utils/BrushMap.kt b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/utils/BrushMap.kt new file mode 100644 index 0000000..fcfea94 --- /dev/null +++ b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/utils/BrushMap.kt @@ -0,0 +1,186 @@ +package com.loopeer.codereaderkt.utils + +import java.util.ArrayList +import java.util.HashMap +import java.util.Map + +class BrushMap { + init { + if (mapping == null) { + mapping = HashMap() + init() + } + } + + companion object { + var mapping: HashMap>? = null + + init { + BrushMap() + } + + fun init() { + var list= mutableListOf() + list.add("actionscript3") + list.add("as3") + mapping!!.put("AS3", list) + list = ArrayList() + list.add("applescript") + list.add("scpt") + mapping!!.put("AppleScript", list) + list = ArrayList() + list.add("bash") + list.add("shell") + list.add("sh") + list.add("rc") + list.add("conf") + mapping!!.put("Bash", list) + list = ArrayList() + list.add("coldfusion") + list.add("cfm") + list.add("cf") + mapping!!.put("ColdFusion", list) + list = ArrayList() + list.add("cpp") + list.add("c") + list.add("cc") + list.add("h") + list.add("hpp") + mapping!!.put("Cpp", list) + list = ArrayList() + list.add("c#") + list.add("c-sharp") + list.add("csharp") + list.add("cs") + mapping!!.put("CSharp", list) + list = ArrayList() + list.add("css") + mapping!!.put("Css", list) + list = ArrayList() + list.add("delphi") + list.add("pascal") + list.add("pas") + list.add("simba") + mapping!!.put("Delphi", list) + list = ArrayList() + list.add("diff") + list.add("patch") + mapping!!.put("Diff", list) + list = ArrayList() + list.add("erl") + list.add("hrl") + mapping!!.put("Erlang", list) + list = ArrayList() + list.add("groovy") + mapping!!.put("Groovy", list) + list = ArrayList() + list.add("java") + mapping!!.put("Java", list) + list = ArrayList() + list.add("jfx") + list.add("javafx") + mapping!!.put("JavaFX", list) + list = ArrayList() + list.add("js") + list.add("jscript") + list.add("javascript") + mapping!!.put("JScript", list) + list = ArrayList() + list.add("perl") + list.add("pl") + mapping!!.put("Perl", list) + list = ArrayList() + list.add("php") + mapping!!.put("Php", list) + list = ArrayList() + list.add("text") + list.add("plain") + list.add("rst") + list.add("txt") + mapping!!.put("Plain", list) + list = ArrayList() + list.add("powershell") + list.add("ps") + list.add("ps1") + mapping!!.put("PowerShell", list) + list = ArrayList() + list.add("py") + list.add("python") + mapping!!.put("Python", list) + list = ArrayList() + list.add("ruby") + list.add("rails") + list.add("ror") + mapping!!.put("Ruby", list) + list = ArrayList() + list.add("sass") + list.add("scss") + mapping!!.put("Sass", list) + list = ArrayList() + list.add("scala") + mapping!!.put("Scala", list) + list = ArrayList() + list.add("sql") + mapping!!.put("Sql", list) + list = ArrayList() + list.add("v") + list.add("sv") + mapping!!.put("Verilog", list) + list = ArrayList() + list.add("vb") + list.add("vbnet") + mapping!!.put("Vb", list) + list = ArrayList() + list.add("xml") + list.add("xslt") + list.add("htm") + list.add("html") + list.add("xhtml") + list.add("xaml") + list.add("iml") + list.add("plist") + list.add("storyboard") + list.add("xcworkspacedata") + list.add("xcscheme") + mapping!!.put("Xml", list) + list = ArrayList() + list.add("gradle") + mapping!!.put("Gradle", list) + list = ArrayList() + list.add("txt") + list.add("bat") + list.add("pbxproj") + mapping!!.put("Txt", list) + list = ArrayList() + list.add("pro") + mapping!!.put("Pro", list) + list = ArrayList() + list.add("properties") + mapping!!.put("Properties", list) + list = ArrayList() + list.add("json") + mapping!!.put("Json", list) + list = ArrayList() + list.add("swift") + mapping!!.put("Swift", list) + list = ArrayList() + list.add("go") + mapping!!.put("Go", list) + list.add("ino") + mapping!!.put("Arduino", list) + } + + fun getJsFileForExtension(paramString: String?): String? { + var paramString = paramString + paramString = paramString?.toLowerCase() + val localIterator = mapping!!.entries.iterator() + while (localIterator.hasNext()) { + val localEntry = localIterator.next() as Map.Entry<*, *> + if ((localEntry.value as List<*>).indexOf(paramString) != -1) { + return localEntry.key .toString() + } + } + return null + } + } +} diff --git a/code-reader-kt/src/main/java/com/loopeer/codereaderkt/utils/ColorUtils.kt b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/utils/ColorUtils.kt new file mode 100644 index 0000000..19ad6bc --- /dev/null +++ b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/utils/ColorUtils.kt @@ -0,0 +1,12 @@ +package com.loopeer.codereaderkt.utils + +import android.content.Context +import android.support.v4.content.ContextCompat + + +object ColorUtils { + + @JvmStatic fun getColorString(context: Context, res: Int): String { + return String.format("#%06X", 0xFFFFFF and ContextCompat.getColor(context, res)) + } +} \ No newline at end of file diff --git a/code-reader-kt/src/main/java/com/loopeer/codereaderkt/utils/CustomTextUtils.kt b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/utils/CustomTextUtils.kt new file mode 100644 index 0000000..4a96b68 --- /dev/null +++ b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/utils/CustomTextUtils.kt @@ -0,0 +1,23 @@ +package com.loopeer.codereaderkt.utils + +import android.text.TextUtils +import java.util.regex.Pattern + + +object CustomTextUtils { + + fun calculateTextStartEnd(strings: String, target: String): IntArray { + val result = IntArray(2) + result[0] = strings.indexOf(target) + result[1] = result[0] + target.length + return result + } + + + fun isEmail(email: String): Boolean { + if (TextUtils.isEmpty(email)) return false + val p = Pattern.compile("\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*")//复杂匹配 + val m = p.matcher(email) + return m.matches() + } +} diff --git a/code-reader-kt/src/main/java/com/loopeer/codereaderkt/utils/DeviceUtils.kt b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/utils/DeviceUtils.kt new file mode 100644 index 0000000..57c96ed --- /dev/null +++ b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/utils/DeviceUtils.kt @@ -0,0 +1,27 @@ +package com.loopeer.codereaderkt.utils + +import android.content.Context +import android.util.TypedValue +import com.loopeer.codereaderkt.CodeReaderApplication + +object DeviceUtils { + + val statusBarHeight: Int + get() { + var result = 0 + val resId = CodeReaderApplication.appContext + .resources.getIdentifier("status_bar_height", "dimen", "android") + if (resId > 0) { + result = CodeReaderApplication.appContext + .resources.getDimensionPixelOffset(resId) + } + return result + } + + fun dpToPx(context: Context, dpValue: Float): Float { + val metrics = context.resources.displayMetrics + return TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, dpValue, metrics) + } + +} + diff --git a/code-reader-kt/src/main/java/com/loopeer/codereaderkt/utils/DownloadFile.kt b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/utils/DownloadFile.kt new file mode 100644 index 0000000..cced374 --- /dev/null +++ b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/utils/DownloadFile.kt @@ -0,0 +1,87 @@ +package com.loopeer.codereaderkt.utils + +import android.content.Context + +import java.io.IOException +import java.io.InputStream +import java.io.OutputStream + +class DownloadFile +/** + * Download a file from a URL somewhere. The download is atomic; that is, it + * downloads to a temporary file, then renames it to the requested file name + * only if the download successfully completes. + + * Returns TRUE if download succeeds, FALSE otherwise. + + */ +(context: Context) { + companion object { + private val TAG = "Download" + private val BUFFER_SIZE = 8192 + + /** + * Copy from one stream to another. Throws IOException in the event of error + * (for example, SD card is full) + + * @param is + * * Input stream. + * * + * @param os + * * Output stream. + * * + * @param buffer + * * Temporary buffer to use for copy. + * * + * @param bufferSize + * * Size of temporary buffer, in bytes. + */ + @Throws(IOException::class) + fun copyStream(`is`: InputStream, os: OutputStream, + buffer: ByteArray, bufferSize: Int, confid: String?, fileSize: Double, + context: Context?) { + var downloaded = 0.0 + val update = IntArray(3) + + //Intent intent = new Intent(); + // TODO + //intent.setAction(Const.BROADCAST + confid); + + try { + while (true) { + val count = `is`.read(buffer, 0, bufferSize) + downloaded += count.toDouble() + if (count == -1) { + if (context != null) { + //intent.putExtra("zipcomplete", 1); + //context.sendBroadcast(intent); + } + break + } + os.write(buffer, 0, count) + + if (context != null) { + update[0] = downloaded.toInt() + update[1] = fileSize.toInt() + update[2] = (downloaded / fileSize * 100).toInt() + //intent.putExtra("zipprogress", update); + //context.sendBroadcast(intent); + } + } + + } catch (e: IOException) { + throw e + } + + } + + fun humanReadableByteCount(bytes: Long, si: Boolean): String { + val unit = if (si) 1000 else 1024 + if (bytes < unit) + return bytes.toString() + " B" + val exp = (Math.log(bytes.toDouble()) / Math.log(unit.toDouble())).toInt() + val pre = (if (si) "KMGTPE" else "KMGTPE")[exp - 1] + if (si) "" else "i" + return String.format("%.2f %sB", bytes / Math.pow(unit.toDouble(), exp.toDouble()), pre) + } + } +} diff --git a/code-reader-kt/src/main/java/com/loopeer/codereaderkt/utils/DownloadUrlParse.kt b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/utils/DownloadUrlParse.kt new file mode 100644 index 0000000..52d4e40 --- /dev/null +++ b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/utils/DownloadUrlParse.kt @@ -0,0 +1,63 @@ +package com.loopeer.codereaderkt.utils + +import android.content.Context +import android.text.TextUtils +import com.loopeer.codereaderkt.Navigator +import com.loopeer.codereaderkt.model.Repo +import java.io.File + + +object DownloadUrlParser { + private val GITHUB_REPO_URL_BASE = "https://codeload.github.com/" + private val ZIP_SUFFIX = ".zip" + + fun parseGithubUrlAndDownload(context: Context, url: String): Boolean { + val downloadUrl = DownloadUrlParser.parseGithubDownloadUrl(url) ?: return false + val repoName = DownloadUrlParser.getRepoName(url) + val repo = Repo(repoName, FileCache().getInstance().getRepoAbsolutePath(repoName), downloadUrl, true, 0) + Navigator().startDownloadNewRepoService(context, repo) + return true + } + + fun parseGithubDownloadUrl(url: String): String? { + if (TextUtils.isEmpty(url)) return null + val sb = StringBuilder() + val strings = url.split("/".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() + if (strings.size < 5) return null + if (strings.size == 5) { + sb.append(GITHUB_REPO_URL_BASE) + sb.append(strings[3]) + sb.append("/") + if (strings[4].contains("?")) { + val lastName = strings[4].split("\\?".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() + sb.append(lastName[0]) + sb.append("/") + } else { + sb.append(strings[4]) + sb.append("/") + } + sb.append("zip/master") + return sb.toString() + } + if (strings.size > 5) { + sb.append(GITHUB_REPO_URL_BASE) + sb.append(strings[3]) + sb.append("/") + sb.append(strings[4]) + sb.append("/") + sb.append("zip/master") + return sb.toString() + } + return null + } + + @JvmStatic fun getRemoteRepoZipFileName(repoName: String): File = + File(FileCache().getInstance().getCacheDir(), getRepoNameZip(repoName)) + + private fun getRepoNameZip(name: String): String = name + ZIP_SUFFIX + + private fun getRepoName(url: String): String { + val strings = url.split("/".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() + return strings[4].split("//.".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()[0] + } +} \ No newline at end of file diff --git a/code-reader-kt/src/main/java/com/loopeer/codereaderkt/utils/FileCache.kt b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/utils/FileCache.kt new file mode 100644 index 0000000..bda2843 --- /dev/null +++ b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/utils/FileCache.kt @@ -0,0 +1,99 @@ +package com.loopeer.codereaderkt.utils + +import android.content.Context +import android.content.pm.PackageManager +import android.os.Environment +import com.loopeer.codereaderkt.CodeReaderApplication +import com.loopeer.codereaderkt.model.DirectoryNode +import java.io.File +import java.util.* + + +class FileCache { + companion object { + private val EXTERNAL_STORAGE_PERMISSION = "android.permission.WRITE_EXTERNAL_STORAGE" + private var instance: FileCache? = null + private val cachePath = Environment.getExternalStorageDirectory().toString() + "/CodeReaderKt/repo/" + private var cacheDir: File? = null + val cacheDirPath = "/repo/" + + } + + + init { + cacheDir = if (hasSDCard() && hasExternalStoragePermission(CodeReaderApplication.appContext)) { + createFilePath(cachePath) + } else { + createFilePath("${CodeReaderApplication.appContext.cacheDir}$cacheDirPath")//字符串拼接 + } + } + + fun createFilePath(filePath: String): File { + return createFilePath(File(filePath)) + } + + private fun createFilePath(file: File): File { + if (!file.exists()) { + file.mkdirs()//mkdir()和mkdirs区别 前者只会建立一级目录,后者可建多级 + } + return file + } + + fun getInstance(): FileCache { + if (null == instance) { + instance = FileCache() + } + return instance as FileCache + } + + fun hasSDCard(): Boolean = Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED + //是否存在SD卡 + + fun getFileDirectoryNode(): DirectoryNode? { + val file = File(cacheDir, "cardStackView") + if (file.listFiles() == null || file.listFiles().isEmpty()) return null + return getFileDirectory(file) + } + + fun getCacheDir(): File? = cacheDir + + fun getRepoAbsolutePath(repoName: String): String = + getCacheDir()!!.path + File.separator + repoName + + + fun getFileDirectory(file: File): DirectoryNode? { + val directoryNode = DirectoryNode() + directoryNode.name = file.name + directoryNode.absolutePath = file.absolutePath + if (file.isDirectory){ + directoryNode.isDirectory = true + directoryNode.pathNodes = ArrayList() + file.listFiles() + .filterNot { it.name.startsWith(".") } + .map { getFileDirectory(it) } + .forEach { (directoryNode.pathNodes as ArrayList).add(it!!) } + if (!directoryNode.pathNodes?.isEmpty()!!){ +// Collections.sort(directoryNode.pathNodes) + } + } + return directoryNode + } + + private fun hasExternalStoragePermission(context: Context): Boolean { + val perm: Int = context.checkCallingOrSelfPermission(EXTERNAL_STORAGE_PERMISSION) + return perm == PackageManager.PERMISSION_GRANTED//是否已经获取sd卡读写权限 + } + + fun deleteFilesByDirectory(directory: File?) { + if (directory != null && directory.exists() && directory.list() != null) { + for (item in directory.listFiles()) { + if (item.isDirectory) { + deleteFilesByDirectory(item) + } else { + item.delete() + } + } + } + } + +} \ No newline at end of file diff --git a/code-reader-kt/src/main/java/com/loopeer/codereaderkt/utils/FileTypeUtils.kt b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/utils/FileTypeUtils.kt new file mode 100644 index 0000000..0be9013 --- /dev/null +++ b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/utils/FileTypeUtils.kt @@ -0,0 +1,191 @@ +package com.loopeer.codereaderkt.utils + +import java.util.HashMap + +object FileTypeUtils { + + // comma separated list of all file extensions supported by the media scanner + var sFileExtensions: String + + // Audio file types + val FILE_TYPE_MP3 = 1 + val FILE_TYPE_M4A = 2 + val FILE_TYPE_WAV = 3 + val FILE_TYPE_AMR = 4 + val FILE_TYPE_AWB = 5 + val FILE_TYPE_WMA = 6 + val FILE_TYPE_OGG = 7 + private val FIRST_AUDIO_FILE_TYPE = FILE_TYPE_MP3 + private val LAST_AUDIO_FILE_TYPE = FILE_TYPE_OGG + + // MIDI file types + val FILE_TYPE_MID = 11 + val FILE_TYPE_SMF = 12 + val FILE_TYPE_IMY = 13 + private val FIRST_MIDI_FILE_TYPE = FILE_TYPE_MID + private val LAST_MIDI_FILE_TYPE = FILE_TYPE_IMY + + // Video file types + val FILE_TYPE_MP4 = 21 + val FILE_TYPE_M4V = 22 + val FILE_TYPE_3GPP = 23 + val FILE_TYPE_3GPP2 = 24 + val FILE_TYPE_WMV = 25 + private val FIRST_VIDEO_FILE_TYPE = FILE_TYPE_MP4 + private val LAST_VIDEO_FILE_TYPE = FILE_TYPE_WMV + + // Image file types + val FILE_TYPE_JPEG = 31 + val FILE_TYPE_GIF = 32 + val FILE_TYPE_PNG = 33 + val FILE_TYPE_BMP = 34 + val FILE_TYPE_WBMP = 35 + private val FIRST_IMAGE_FILE_TYPE = FILE_TYPE_JPEG + private val LAST_IMAGE_FILE_TYPE = FILE_TYPE_WBMP + + // Playlist file types + val FILE_TYPE_M3U = 41 + val FILE_TYPE_PLS = 42 + val FILE_TYPE_WPL = 43 + private val FIRST_PLAYLIST_FILE_TYPE = FILE_TYPE_M3U + private val LAST_PLAYLIST_FILE_TYPE = FILE_TYPE_WPL + + // MarkDown file types + val FILE_TYPE_MARKDOWN = 44 + val FILE_TYPE_MD = 45 + private val FIRST_MARKDOWNLIST_FILE_TYPE = FILE_TYPE_MARKDOWN + private val LAST_MARKDOWNLIST_FILE_TYPE = FILE_TYPE_MD + + //静态内部类 + class MediaFileType(var fileType: Int, var mimeType: String) + + private val sFileTypeMap = HashMap() + private val sMimeTypeMap = HashMap() + + internal fun addFileType(extension: String, fileType: Int, mimeType: String) { + sFileTypeMap.put(extension, MediaFileType(fileType, mimeType)) + sMimeTypeMap.put(mimeType, fileType) + } + + init { + addFileType("MP3", FILE_TYPE_MP3, "audio/mpeg") + addFileType("M4A", FILE_TYPE_M4A, "audio/mp4") + addFileType("WAV", FILE_TYPE_WAV, "audio/x-wav") + addFileType("AMR", FILE_TYPE_AMR, "audio/amr") + addFileType("AWB", FILE_TYPE_AWB, "audio/amr-wb") + addFileType("WMA", FILE_TYPE_WMA, "audio/x-ms-wma") + addFileType("OGG", FILE_TYPE_OGG, "application/ogg") + + addFileType("MID", FILE_TYPE_MID, "audio/midi") + addFileType("XMF", FILE_TYPE_MID, "audio/midi") + addFileType("RTTTL", FILE_TYPE_MID, "audio/midi") + addFileType("SMF", FILE_TYPE_SMF, "audio/sp-midi") + addFileType("IMY", FILE_TYPE_IMY, "audio/imelody") + + addFileType("MP4", FILE_TYPE_MP4, "video/mp4") + addFileType("M4V", FILE_TYPE_M4V, "video/mp4") + addFileType("3GP", FILE_TYPE_3GPP, "video/3gpp") + addFileType("3GPP", FILE_TYPE_3GPP, "video/3gpp") + addFileType("3G2", FILE_TYPE_3GPP2, "video/3gpp2") + addFileType("3GPP2", FILE_TYPE_3GPP2, "video/3gpp2") + addFileType("WMV", FILE_TYPE_WMV, "video/x-ms-wmv") + + addFileType("JPG", FILE_TYPE_JPEG, "image/jpeg") + addFileType("JPEG", FILE_TYPE_JPEG, "image/jpeg") + addFileType("GIF", FILE_TYPE_GIF, "image/gif") + addFileType("PNG", FILE_TYPE_PNG, "image/png") + addFileType("BMP", FILE_TYPE_BMP, "image/x-ms-bmp") + addFileType("WBMP", FILE_TYPE_WBMP, "image/vnd.wap.wbmp") + + addFileType("M3U", FILE_TYPE_M3U, "audio/x-mpegurl") + addFileType("PLS", FILE_TYPE_PLS, "audio/x-scpls") + addFileType("WPL", FILE_TYPE_WPL, "application/vnd.ms-wpl") + + addFileType("MD", FILE_TYPE_MD, "text/markdown") + addFileType("MARKDOWN", FILE_TYPE_MARKDOWN, "text/markdown") + + // compute file extensions list for native Media Scanner + val builder = StringBuilder() + val iterator = sFileTypeMap.keys.iterator() + + while (iterator.hasNext()) { + if (builder.length > 0) { + builder.append(',') + } + builder.append(iterator.next()) + } + sFileExtensions = builder.toString() + } + + val UNKNOWN_STRING = "" + + private fun isAudioFileType(fileType: Int): Boolean { + return fileType >= FIRST_AUDIO_FILE_TYPE && fileType <= LAST_AUDIO_FILE_TYPE || fileType >= FIRST_MIDI_FILE_TYPE && fileType <= LAST_MIDI_FILE_TYPE + } + + private fun isVideoFileType(fileType: Int): Boolean { + return fileType >= FIRST_VIDEO_FILE_TYPE && fileType <= LAST_VIDEO_FILE_TYPE + } + + private fun isImageFileType(fileType: Int): Boolean { + return fileType >= FIRST_IMAGE_FILE_TYPE && fileType <= LAST_IMAGE_FILE_TYPE + } + + private fun isPlayListFileType(fileType: Int): Boolean { + return fileType >= FIRST_PLAYLIST_FILE_TYPE && fileType <= LAST_PLAYLIST_FILE_TYPE + } + + private fun isMarkDownFileType(fileType: Int): Boolean { + return fileType >= FIRST_MARKDOWNLIST_FILE_TYPE && fileType <= LAST_MARKDOWNLIST_FILE_TYPE + } + + fun getFileType(path: String?): MediaFileType? { + if(path!=null){ + val lastDot = path?.lastIndexOf(".") + if (lastDot < 0) + return null + return sFileTypeMap[path.substring(lastDot + 1).toUpperCase()] + } + return null + } + + fun isVideoFileType(path: String): Boolean { + val type = getFileType(path) + if (null != type) { + return isVideoFileType(type.fileType) + } + return false + } + + fun isAudioFileType(path: String): Boolean { + val type = getFileType(path) + if (null != type) { + return isAudioFileType(type.fileType) + } + return false + } + + fun isImageFileType(path: String?): Boolean { + val type = getFileType(path) + if (null != type) { + return isImageFileType(type.fileType) + } + return false + } + + fun getFileTypeForMimeType(mimeType: String): Int { + val value = sMimeTypeMap[mimeType] + return value?.toInt() ?: 0 + } + + fun isMdFileType(path: String?): Boolean { + val type = getFileType(path) + if (null != type) { + return isMarkDownFileType(type.fileType) + } + return false + } + + + +} diff --git a/code-reader-kt/src/main/java/com/loopeer/codereaderkt/utils/HtmlParser.kt b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/utils/HtmlParser.kt new file mode 100644 index 0000000..b6a5903 --- /dev/null +++ b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/utils/HtmlParser.kt @@ -0,0 +1,53 @@ +package com.loopeer.codereaderkt.utils + +import android.content.Context +import android.os.Build +import com.loopeer.codereaderkt.R +import java.io.IOException + + +object HtmlParser { + fun buildHtmlContent(context: Context, paramString1: String, jsFile: String, fileName: String): String { + var jsFile = jsFile + while (true) { + try { + val inputStream = context.assets.open("code.html") + var i =inputStream.available() + var localObject: Any = ByteArray(i) + inputStream.read(localObject as ByteArray) + inputStream.close() + localObject = String(localObject) + val localStringBuilder = StringBuilder() + localStringBuilder.append("SyntaxHighlighter.defaults['auto-links'] = false;") + localStringBuilder.append("SyntaxHighlighter.defaults['toolbar'] = false;") + localStringBuilder.append("SyntaxHighlighter.defaults['wrap-lines'] = false;") + localStringBuilder.append("SyntaxHighlighter.defaults['quick-code'] = false;") + if (!PrefUtils.getPrefDisplayLineNumber(context)) { + localStringBuilder.append("SyntaxHighlighter.defaults['gutter'] = false;") + } + localStringBuilder.append("SyntaxHighlighter.all();") + var temp = "" + if (Build.VERSION.SDK_INT < 14) { + temp = "$('.syntaxhighlighter').css('overflow', 'visible !important');" + } + jsFile = localObject + .replace("!FONT_SIZE!", String.format("", *arrayOf(java.lang.Float.valueOf(PrefUtils.getPrefFontSize(context))))) + .replace("!FILENAME!", fileName) + .replace("!BRUSHJSFILE!", jsFile) + .replace("!SYNTAXHIGHLIGHTER!", localStringBuilder.toString()) + .replace("!JS_FIX_HSCROLL!", temp) + temp = "" + return jsFile + .replace("!STYLE_MENLO!", if (PrefUtils.getPrefMenlofont(context)) temp else "") + .replace("!THEME!", PrefUtils.getPrefTheme(context)) + .replace("!CODE!", paramString1) + + .replace("!WINDOW_BACK_GROUND_COLOR!", ColorUtils.getColorString(context, R.color.code_read_background_color)) + } catch (e: IOException) { + throw RuntimeException(e) + } finally { + + } + } + } +} \ No newline at end of file diff --git a/code-reader-kt/src/main/java/com/loopeer/codereaderkt/utils/PageLinkParser.kt b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/utils/PageLinkParser.kt new file mode 100644 index 0000000..3504714 --- /dev/null +++ b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/utils/PageLinkParser.kt @@ -0,0 +1,85 @@ +package com.loopeer.codereaderkt.utils + +import android.text.TextUtils +import retrofit2.Response + + +class PageLinkParser(response: Response<*>) { + + var first: Int = 0 + private set + var last: Int = 0 + private set + var next: Int = 0 + private set + var prev: Int = 0 + private set + + val remain: Int = Integer.parseInt(response.headers().get("X-RateLimit-Remaining")) + + init { + + val linkHeader = response.headers().get("Link") + if (TextUtils.isEmpty(linkHeader)){} +// return + + val links = linkHeader.split(SPLIT_LINKS.toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() + for (link in links) { + val params = link.split(SPLIT_LINK_PARAM.toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() + if (params.size < 2) + continue + + var url = params[0].trim { it <= ' ' } + if (!url.startsWith("<") || !url.endsWith(">")) + continue + url = url.substring(1, url.length - 1) + + for (i in 1..params.size - 1) { + val rel = params[i].trim { it <= ' ' }.split("=".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() + if (rel.size < 2 || REL_KEY != rel[0]) + continue + + var relValue = rel[1] + if (relValue.startsWith("\"") && relValue.endsWith("\"")) + relValue = relValue.substring(1, relValue.length - 1) + + if (REL_VALUE_FIRST == relValue) + first = getParam(url) + else if (REL_VALUE_LAST == relValue) + last = getParam(url) + else if (REL_VALUE_NEXT == relValue) + next = getParam(url) + else if (REL_VALUE_PREV == relValue) + prev = getParam(url) + } + } + } + + private fun getParam(url: String): Int { + if (TextUtils.isEmpty(url)) + return 0 + val params = url.split("&".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() + for (param in params) { + val parts = param.split("=".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray() + if (parts.size != 2) + continue + if ("page" != parts[0]) + continue + return Integer.parseInt(parts[1]) + } + return 0 + } + + companion object { + + private val SPLIT_LINKS = "," + private val SPLIT_LINK_PARAM = ";" + + private val REL_KEY = "rel" + + private val REL_VALUE_LAST = "last" + private val REL_VALUE_NEXT = "next" + private val REL_VALUE_FIRST = "first" + private val REL_VALUE_PREV = "prev" + } +} \ No newline at end of file diff --git a/code-reader-kt/src/main/java/com/loopeer/codereaderkt/utils/PrefUtils.kt b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/utils/PrefUtils.kt new file mode 100644 index 0000000..f928cb0 --- /dev/null +++ b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/utils/PrefUtils.kt @@ -0,0 +1,55 @@ +package com.loopeer.codereaderkt.utils + +import android.content.Context +import android.preference.PreferenceManager + + +object PrefUtils { + + val PREF_FONT_SIZE = "pref_font_size" + val PREF_DISPLAY_LINE_NUMBER = "pref_display_line_number" + val PREF_MENLO_FONT = "pref_menlo_font" + val PREF_THEME = "pref_theme" + + @JvmStatic fun getPrefFontSize(context: Context): Float { + val sp = PreferenceManager.getDefaultSharedPreferences(context) + return sp.getFloat(PREF_FONT_SIZE, 12f) + } + + fun setPrefFontSize(context: Context, size: Float) { + val sp = PreferenceManager.getDefaultSharedPreferences(context) + sp.edit().putFloat(PREF_FONT_SIZE, size).apply() + } + + @JvmStatic + fun getPrefTheme(context: Context): String { + val sp = PreferenceManager.getDefaultSharedPreferences(context) + return sp.getString(PREF_THEME, "Default") + } + + fun setPrefTheme(context: Context, theme: String) { + val sp = PreferenceManager.getDefaultSharedPreferences(context) + sp.edit().putString(PREF_THEME, theme).apply() + } + + fun getPrefMenlofont(context: Context): Boolean { + val sp = PreferenceManager.getDefaultSharedPreferences(context) + return sp.getBoolean(PREF_MENLO_FONT, true) + } + + fun setPrefMenlofont(context: Context, b: Boolean) { + val sp = PreferenceManager.getDefaultSharedPreferences(context) + sp.edit().putBoolean(PREF_MENLO_FONT, b).apply() + } + + fun getPrefDisplayLineNumber(context: Context): Boolean { + val sp = PreferenceManager.getDefaultSharedPreferences(context) + return sp.getBoolean(PREF_DISPLAY_LINE_NUMBER, true) + } + + fun setPrefDisplayLineNumber(context: Context, b: Boolean) { + val sp = PreferenceManager.getDefaultSharedPreferences(context) + sp.edit().putBoolean(PREF_DISPLAY_LINE_NUMBER, b).apply() + } + +} \ No newline at end of file diff --git a/code-reader-kt/src/main/java/com/loopeer/codereaderkt/utils/RxBus.kt b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/utils/RxBus.kt new file mode 100644 index 0000000..8634fbe --- /dev/null +++ b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/utils/RxBus.kt @@ -0,0 +1,36 @@ +package com.loopeer.codereaderkt.utils + +import rx.Observable +import rx.subjects.PublishSubject +import rx.subjects.SerializedSubject +import rx.subjects.Subject + +class RxBus private constructor() { + + private val _bus = SerializedSubject(PublishSubject.create()) + + fun send(o: Any) { + _bus.onNext(o) + } + + fun toObservable(): Observable { + return _bus + } + + companion object { + + @Volatile private var mDefaultInstance: RxBus? = null + + val instance: RxBus? + get() { + if (mDefaultInstance == null) { + synchronized(RxBus::class.java) { + if (mDefaultInstance == null) { + mDefaultInstance = RxBus() + } + } + } + return mDefaultInstance + } + } +} \ No newline at end of file diff --git a/code-reader-kt/src/main/java/com/loopeer/codereaderkt/utils/SnackbarUtils.kt b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/utils/SnackbarUtils.kt new file mode 100644 index 0000000..4acacac --- /dev/null +++ b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/utils/SnackbarUtils.kt @@ -0,0 +1,17 @@ +package com.loopeer.codereaderkt.utils + +import android.support.annotation.StringRes +import android.support.design.widget.Snackbar +import android.view.View + + +object SnackbarUtils { + + fun show(view: View, s: String) { + Snackbar.make(view, s, Snackbar.LENGTH_SHORT).show() + } + + fun show(view: View, @StringRes id: Int) { + Snackbar.make(view, id, Snackbar.LENGTH_SHORT).show() + } +} \ No newline at end of file diff --git a/code-reader-kt/src/main/java/com/loopeer/codereaderkt/utils/ThemeUtils.kt b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/utils/ThemeUtils.kt new file mode 100644 index 0000000..dcc4bcc --- /dev/null +++ b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/utils/ThemeUtils.kt @@ -0,0 +1,18 @@ +package com.loopeer.codereaderkt.utils + +import android.content.Context +import android.support.v7.app.AppCompatDelegate + + +object ThemeUtils { + val THEME_DAY = "Default" + val THEME_NIGHT = "Night" + + @AppCompatDelegate.NightMode + fun getCurrentNightMode(context: Context): Int { + return if (PrefUtils.getPrefTheme(context) == THEME_DAY) + AppCompatDelegate.MODE_NIGHT_NO + else + AppCompatDelegate.MODE_NIGHT_YES + } +} diff --git a/code-reader-kt/src/main/java/com/loopeer/codereaderkt/utils/Unzip.kt b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/utils/Unzip.kt new file mode 100644 index 0000000..a48b50e --- /dev/null +++ b/code-reader-kt/src/main/java/com/loopeer/codereaderkt/utils/Unzip.kt @@ -0,0 +1,110 @@ +package com.loopeer.codereaderkt.utils + +import android.content.Context +import android.util.Log +import rx.exceptions.Exceptions + +import java.io.BufferedOutputStream +import java.io.File +import java.io.FileInputStream +import java.io.FileOutputStream +import java.io.IOException +import java.io.OutputStream +import java.util.zip.ZipEntry +import java.util.zip.ZipInputStream + +class Unzip +/** + * Constructor. + + * @param mfin Fully-qualified path to .zip file + * * + * @param location Fully-qualified path to folder where files should be written. + * * Path must have a trailing slash. + */ +(private val mfin: FileInputStream, private val mLocation: String?, private val mContext: Context) { + private val mBuffer: ByteArray + + init { + mBuffer = ByteArray(BUFFER_SIZE) + dirChecker(null) + } + + fun DecompressZip() { + var zin: ZipInputStream? = null + var fout: OutputStream? = null + val outputDir = File(mLocation) + var tmp: File? = null + try { + zin = ZipInputStream(mfin) + var ze: ZipEntry? + while (true) { + ze=zin.nextEntry ?: break + if (ze.isDirectory) { + dirChecker(ze) + } else { + tmp = File.createTempFile("decomp", ".tmp", outputDir) + fout = BufferedOutputStream(FileOutputStream(tmp!!)) + DownloadFile.copyStream(zin, fout, mBuffer, BUFFER_SIZE, null, 0.0, null) + zin.closeEntry() + fout.close() + fout = null + tmp.renameTo(File(getPathSaveName(ze))) + tmp = null + } + } + zin.close() + zin = null + } catch (e: Exception) { + throw RuntimeException(e) + } finally { + if (tmp != null) { + try { + tmp.delete() + } catch (ignore: Exception) { + } + + } + if (fout != null) { + try { + fout.close() + } catch (ignore: Exception) { + } + + } + if (zin != null) { + try { + zin.closeEntry() + } catch (ignore: Exception) { + } + + } + if (mfin != null) { + try { + mfin.close() + } catch (ignore: Exception) { + } + + } + } + } + + private fun getPathSaveName(ze: ZipEntry?): String? { + if (ze == null) { + return mLocation + } + val zeName = ze.name + return mLocation + File.separator + zeName.substring(zeName.indexOf("/") + 1, zeName.length) + } + + private fun dirChecker(ze: ZipEntry?) { + val f = File(getPathSaveName(ze)) + if (!f.isDirectory) { + f.mkdirs() + } + } + + companion object { + private val BUFFER_SIZE = 8192 + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable-v21/ic_menu_camera.xml b/code-reader-kt/src/main/res/drawable-v21/ic_menu_camera.xml similarity index 100% rename from app/src/main/res/drawable-v21/ic_menu_camera.xml rename to code-reader-kt/src/main/res/drawable-v21/ic_menu_camera.xml diff --git a/app/src/main/res/drawable-v21/ic_menu_gallery.xml b/code-reader-kt/src/main/res/drawable-v21/ic_menu_gallery.xml similarity index 100% rename from app/src/main/res/drawable-v21/ic_menu_gallery.xml rename to code-reader-kt/src/main/res/drawable-v21/ic_menu_gallery.xml diff --git a/app/src/main/res/drawable-v21/ic_menu_manage.xml b/code-reader-kt/src/main/res/drawable-v21/ic_menu_manage.xml similarity index 100% rename from app/src/main/res/drawable-v21/ic_menu_manage.xml rename to code-reader-kt/src/main/res/drawable-v21/ic_menu_manage.xml diff --git a/app/src/main/res/drawable-v21/ic_menu_send.xml b/code-reader-kt/src/main/res/drawable-v21/ic_menu_send.xml similarity index 100% rename from app/src/main/res/drawable-v21/ic_menu_send.xml rename to code-reader-kt/src/main/res/drawable-v21/ic_menu_send.xml diff --git a/app/src/main/res/drawable-v21/ic_menu_share.xml b/code-reader-kt/src/main/res/drawable-v21/ic_menu_share.xml similarity index 100% rename from app/src/main/res/drawable-v21/ic_menu_share.xml rename to code-reader-kt/src/main/res/drawable-v21/ic_menu_share.xml diff --git a/app/src/main/res/drawable-v21/ic_menu_slideshow.xml b/code-reader-kt/src/main/res/drawable-v21/ic_menu_slideshow.xml similarity index 100% rename from app/src/main/res/drawable-v21/ic_menu_slideshow.xml rename to code-reader-kt/src/main/res/drawable-v21/ic_menu_slideshow.xml diff --git a/app/src/main/res/drawable-xxhdpi/ic_cloud.png b/code-reader-kt/src/main/res/drawable-xxhdpi/ic_cloud.png similarity index 100% rename from app/src/main/res/drawable-xxhdpi/ic_cloud.png rename to code-reader-kt/src/main/res/drawable-xxhdpi/ic_cloud.png diff --git a/app/src/main/res/drawable-xxhdpi/ic_cloud_download.png b/code-reader-kt/src/main/res/drawable-xxhdpi/ic_cloud_download.png similarity index 100% rename from app/src/main/res/drawable-xxhdpi/ic_cloud_download.png rename to code-reader-kt/src/main/res/drawable-xxhdpi/ic_cloud_download.png diff --git a/app/src/main/res/drawable-xxhdpi/ic_delete_white.png b/code-reader-kt/src/main/res/drawable-xxhdpi/ic_delete_white.png similarity index 100% rename from app/src/main/res/drawable-xxhdpi/ic_delete_white.png rename to code-reader-kt/src/main/res/drawable-xxhdpi/ic_delete_white.png diff --git a/app/src/main/res/drawable-xxhdpi/ic_directory_file.png b/code-reader-kt/src/main/res/drawable-xxhdpi/ic_directory_file.png similarity index 100% rename from app/src/main/res/drawable-xxhdpi/ic_directory_file.png rename to code-reader-kt/src/main/res/drawable-xxhdpi/ic_directory_file.png diff --git a/app/src/main/res/drawable-xxhdpi/ic_directory_path.png b/code-reader-kt/src/main/res/drawable-xxhdpi/ic_directory_path.png similarity index 100% rename from app/src/main/res/drawable-xxhdpi/ic_directory_path.png rename to code-reader-kt/src/main/res/drawable-xxhdpi/ic_directory_path.png diff --git a/app/src/main/res/drawable-xxhdpi/ic_document_white.png b/code-reader-kt/src/main/res/drawable-xxhdpi/ic_document_white.png similarity index 100% rename from app/src/main/res/drawable-xxhdpi/ic_document_white.png rename to code-reader-kt/src/main/res/drawable-xxhdpi/ic_document_white.png diff --git a/app/src/main/res/drawable-xxhdpi/ic_empty.png b/code-reader-kt/src/main/res/drawable-xxhdpi/ic_empty.png similarity index 100% rename from app/src/main/res/drawable-xxhdpi/ic_empty.png rename to code-reader-kt/src/main/res/drawable-xxhdpi/ic_empty.png diff --git a/app/src/main/res/drawable-xxhdpi/ic_folder_white.png b/code-reader-kt/src/main/res/drawable-xxhdpi/ic_folder_white.png similarity index 100% rename from app/src/main/res/drawable-xxhdpi/ic_folder_white.png rename to code-reader-kt/src/main/res/drawable-xxhdpi/ic_folder_white.png diff --git a/app/src/main/res/drawable-xxhdpi/ic_github.png b/code-reader-kt/src/main/res/drawable-xxhdpi/ic_github.png similarity index 100% rename from app/src/main/res/drawable-xxhdpi/ic_github.png rename to code-reader-kt/src/main/res/drawable-xxhdpi/ic_github.png diff --git a/app/src/main/res/drawable-xxhdpi/ic_go_out.png b/code-reader-kt/src/main/res/drawable-xxhdpi/ic_go_out.png similarity index 100% rename from app/src/main/res/drawable-xxhdpi/ic_go_out.png rename to code-reader-kt/src/main/res/drawable-xxhdpi/ic_go_out.png diff --git a/app/src/main/res/drawable-xxhdpi/ic_library_add_white.png b/code-reader-kt/src/main/res/drawable-xxhdpi/ic_library_add_white.png similarity index 100% rename from app/src/main/res/drawable-xxhdpi/ic_library_add_white.png rename to code-reader-kt/src/main/res/drawable-xxhdpi/ic_library_add_white.png diff --git a/code-reader-kt/src/main/res/drawable-xxhdpi/ic_menu_github.png b/code-reader-kt/src/main/res/drawable-xxhdpi/ic_menu_github.png new file mode 100644 index 0000000..88b856e Binary files /dev/null and b/code-reader-kt/src/main/res/drawable-xxhdpi/ic_menu_github.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_phone.png b/code-reader-kt/src/main/res/drawable-xxhdpi/ic_phone.png similarity index 100% rename from app/src/main/res/drawable-xxhdpi/ic_phone.png rename to code-reader-kt/src/main/res/drawable-xxhdpi/ic_phone.png diff --git a/app/src/main/res/drawable-xxhdpi/ic_public_white.png b/code-reader-kt/src/main/res/drawable-xxhdpi/ic_public_white.png similarity index 100% rename from app/src/main/res/drawable-xxhdpi/ic_public_white.png rename to code-reader-kt/src/main/res/drawable-xxhdpi/ic_public_white.png diff --git a/code-reader-kt/src/main/res/drawable-xxhdpi/ic_pwd_unvisible.png b/code-reader-kt/src/main/res/drawable-xxhdpi/ic_pwd_unvisible.png new file mode 100644 index 0000000..df12966 Binary files /dev/null and b/code-reader-kt/src/main/res/drawable-xxhdpi/ic_pwd_unvisible.png differ diff --git a/code-reader-kt/src/main/res/drawable-xxhdpi/ic_pwd_visible.png b/code-reader-kt/src/main/res/drawable-xxhdpi/ic_pwd_visible.png new file mode 100644 index 0000000..bf9197b Binary files /dev/null and b/code-reader-kt/src/main/res/drawable-xxhdpi/ic_pwd_visible.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_repo_white.png b/code-reader-kt/src/main/res/drawable-xxhdpi/ic_repo_white.png similarity index 100% rename from app/src/main/res/drawable-xxhdpi/ic_repo_white.png rename to code-reader-kt/src/main/res/drawable-xxhdpi/ic_repo_white.png diff --git a/app/src/main/res/drawable-xxhdpi/ic_save_white.png b/code-reader-kt/src/main/res/drawable-xxhdpi/ic_save_white.png similarity index 100% rename from app/src/main/res/drawable-xxhdpi/ic_save_white.png rename to code-reader-kt/src/main/res/drawable-xxhdpi/ic_save_white.png diff --git a/app/src/main/res/drawable-xxhdpi/ic_search_white.png b/code-reader-kt/src/main/res/drawable-xxhdpi/ic_search_white.png similarity index 100% rename from app/src/main/res/drawable-xxhdpi/ic_search_white.png rename to code-reader-kt/src/main/res/drawable-xxhdpi/ic_search_white.png diff --git a/app/src/main/res/drawable-xxhdpi/ic_settings_white.png b/code-reader-kt/src/main/res/drawable-xxhdpi/ic_settings_white.png similarity index 100% rename from app/src/main/res/drawable-xxhdpi/ic_settings_white.png rename to code-reader-kt/src/main/res/drawable-xxhdpi/ic_settings_white.png diff --git a/app/src/main/res/drawable-xxhdpi/ic_trending.png b/code-reader-kt/src/main/res/drawable-xxhdpi/ic_trending.png similarity index 100% rename from app/src/main/res/drawable-xxhdpi/ic_trending.png rename to code-reader-kt/src/main/res/drawable-xxhdpi/ic_trending.png diff --git a/app/src/main/res/drawable-xxhdpi/ic_triangle_close.png b/code-reader-kt/src/main/res/drawable-xxhdpi/ic_triangle_close.png similarity index 100% rename from app/src/main/res/drawable-xxhdpi/ic_triangle_close.png rename to code-reader-kt/src/main/res/drawable-xxhdpi/ic_triangle_close.png diff --git a/app/src/main/res/drawable-xxhdpi/ic_triangle_open.png b/code-reader-kt/src/main/res/drawable-xxhdpi/ic_triangle_open.png similarity index 100% rename from app/src/main/res/drawable-xxhdpi/ic_triangle_open.png rename to code-reader-kt/src/main/res/drawable-xxhdpi/ic_triangle_open.png diff --git a/app/src/main/res/drawable-xxhdpi/ic_view_list_white.png b/code-reader-kt/src/main/res/drawable-xxhdpi/ic_view_list_white.png similarity index 100% rename from app/src/main/res/drawable-xxhdpi/ic_view_list_white.png rename to code-reader-kt/src/main/res/drawable-xxhdpi/ic_view_list_white.png diff --git a/app/src/main/res/drawable/bg_button.xml b/code-reader-kt/src/main/res/drawable/bg_button.xml similarity index 100% rename from app/src/main/res/drawable/bg_button.xml rename to code-reader-kt/src/main/res/drawable/bg_button.xml diff --git a/app/src/main/res/drawable/progress_horizontal_web_view.xml b/code-reader-kt/src/main/res/drawable/progress_horizontal_web_view.xml similarity index 100% rename from app/src/main/res/drawable/progress_horizontal_web_view.xml rename to code-reader-kt/src/main/res/drawable/progress_horizontal_web_view.xml diff --git a/app/src/main/res/drawable/selector_directory_open_close.xml b/code-reader-kt/src/main/res/drawable/selector_directory_open_close.xml similarity index 100% rename from app/src/main/res/drawable/selector_directory_open_close.xml rename to code-reader-kt/src/main/res/drawable/selector_directory_open_close.xml diff --git a/code-reader-kt/src/main/res/drawable/selector_pwd_visibility.xml b/code-reader-kt/src/main/res/drawable/selector_pwd_visibility.xml new file mode 100644 index 0000000..462b029 --- /dev/null +++ b/code-reader-kt/src/main/res/drawable/selector_pwd_visibility.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/shape_auto_complete_background.xml b/code-reader-kt/src/main/res/drawable/shape_auto_complete_background.xml similarity index 100% rename from app/src/main/res/drawable/shape_auto_complete_background.xml rename to code-reader-kt/src/main/res/drawable/shape_auto_complete_background.xml diff --git a/app/src/main/res/drawable/shape_circle_document.xml b/code-reader-kt/src/main/res/drawable/shape_circle_document.xml similarity index 100% rename from app/src/main/res/drawable/shape_circle_document.xml rename to code-reader-kt/src/main/res/drawable/shape_circle_document.xml diff --git a/app/src/main/res/drawable/shape_circle_folder.xml b/code-reader-kt/src/main/res/drawable/shape_circle_folder.xml similarity index 100% rename from app/src/main/res/drawable/shape_circle_folder.xml rename to code-reader-kt/src/main/res/drawable/shape_circle_folder.xml diff --git a/app/src/main/res/drawable/side_nav_bar.xml b/code-reader-kt/src/main/res/drawable/side_nav_bar.xml similarity index 100% rename from app/src/main/res/drawable/side_nav_bar.xml rename to code-reader-kt/src/main/res/drawable/side_nav_bar.xml diff --git a/app/src/main/res/drawable/theme_shape_day.xml b/code-reader-kt/src/main/res/drawable/theme_shape_day.xml similarity index 100% rename from app/src/main/res/drawable/theme_shape_day.xml rename to code-reader-kt/src/main/res/drawable/theme_shape_day.xml diff --git a/app/src/main/res/drawable/theme_shape_night.xml b/code-reader-kt/src/main/res/drawable/theme_shape_night.xml similarity index 100% rename from app/src/main/res/drawable/theme_shape_night.xml rename to code-reader-kt/src/main/res/drawable/theme_shape_night.xml diff --git a/code-reader-kt/src/main/res/layout/activity_about.xml b/code-reader-kt/src/main/res/layout/activity_about.xml new file mode 100644 index 0000000..cc40b59 --- /dev/null +++ b/code-reader-kt/src/main/res/layout/activity_about.xml @@ -0,0 +1,77 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/code-reader-kt/src/main/res/layout/activity_add_repo.xml b/code-reader-kt/src/main/res/layout/activity_add_repo.xml new file mode 100644 index 0000000..135bb32 --- /dev/null +++ b/code-reader-kt/src/main/res/layout/activity_add_repo.xml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + +